diff --git a/.github/workflows/rdoc.yaml b/.github/workflows/rdoc.yaml new file mode 100644 index 00000000000..8be8ddfb9c8 --- /dev/null +++ b/.github/workflows/rdoc.yaml @@ -0,0 +1,37 @@ +name: RDoc Documentation + +on: + pull_request: + workflow_dispatch: + +permissions: {} + +env: + BUNDLE_WITH: docs + +jobs: + build: + name: Build and verify RDoc documentation + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0 + with: + ruby-version: '3.3' + bundler-cache: true + - name: Run strict documentation checks + run: bundle exec rake docs:check + - name: Check reproducible output + run: bundle exec rake docs:build_twice + - name: Check gem installation documentation + run: bundle exec ruby tool/docs/gem_install_check.rb + - name: Upload RDoc artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: rdoc-site + path: | + tmp/rdoc-site + tmp/rdoc-api-compatibility.json + tmp/rdoc-link-report.json + tmp/rdoc-ref-report.json diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index 3186c38e8df..f80528afc2b 100644 --- a/.github/workflows/website.yaml +++ b/.github/workflows/website.yaml @@ -19,7 +19,7 @@ on: permissions: {} env: - BUNDLE_WITH: jekyll_plugins + BUNDLE_WITH: docs jobs: website: if: ${{ inputs.publish_website || github.ref_name }} @@ -39,16 +39,24 @@ jobs: with: ruby-version: '3.1' - run: bundle install - - name: Build HTML, reindex - env: - ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }} + - run: mkdir -p tmp + - name: Snapshot existing versioned API docs + run: bundle exec ruby tool/docs/publish_check.rb snapshot --pages gh-pages --snapshot tmp/gh-pages-api-doc-before.json + - name: Build RDoc site run: | - bundle exec rake site:fetch_latest site:build_doc site:update_search_index site:clean_html site:build_html + bundle exec rake docs:check + bundle exec rake docs:build_twice + bundle exec ruby tool/docs/gem_install_check.rb + rsync -a --delete --exclude '.git' --exclude 'api-doc' tmp/rdoc-site/ gh-pages/ + touch gh-pages/.nojekyll + - name: Verify versioned API docs were preserved + run: bundle exec ruby tool/docs/publish_check.rb verify --pages gh-pages --snapshot tmp/gh-pages-api-doc-before.json - 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 + git -C gh-pages config user.name "$(git log --format="%aN" -n 1)" + git -C gh-pages config user.email "$(git log --format="%aE" -n 1)" + git -C gh-pages add -A + git -C gh-pages commit --allow-empty -m "Update documentation site" - name: Deploy to GitHub pages via gh-pages branch uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 with: @@ -56,7 +64,7 @@ jobs: publish_dir: ./gh-pages api_docs: needs: website - if: ${{ inputs.publish_version || github.ref_name }} + if: ${{ inputs.publish_version || startsWith(github.ref, 'refs/tags/v') }} permissions: contents: write name: Publish API Docs @@ -65,7 +73,7 @@ jobs: - name: Checkout release tag uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ref: ${{ env.GITHUB_REF }} + ref: ${{ github.ref }} - name: Checkout GitHub pages branch uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -75,18 +83,28 @@ jobs: with: ruby-version: '3.2' - run: bundle install - - name: Build API docs + - run: mkdir -p tmp + - name: Snapshot existing versioned API docs + run: bundle exec ruby tool/docs/publish_check.rb snapshot --pages gh-pages --snapshot tmp/gh-pages-api-doc-before.json + - name: Build versioned API docs env: - PUBLISH_VERSION: ${{ inputs.publish_version || env.GITHUB_REF }} + PUBLISH_VERSION: ${{ inputs.publish_version || github.ref_name }} run: | - bundle exec rake site:fetch_latest "apidocs:gen_version[${PUBLISH_VERSION}]" + VERSION="${PUBLISH_VERSION#v}" + bundle exec rake "docs:rdoc:build_version[${VERSION}]" + bundle exec ruby tool/docs/version_check.rb --root "tmp/rdoc-api/${VERSION}" --version "${VERSION}" + bundle exec ruby tool/docs/compatibility.rb --root "tmp/rdoc-api/${VERSION}" --rdoc "tmp/rdoc-api/${VERSION}/js/search_data.js" --strict + bundle exec ruby tool/docs/link_checker.rb --root "tmp/rdoc-api/${VERSION}" --allow-root-links --root-links gh-pages --strict + bundle exec ruby tool/docs/rdoc_ref_checker.rb --root "tmp/rdoc-api/${VERSION}" + mkdir -p "gh-pages/api-doc/${VERSION}" + rsync -a --delete "tmp/rdoc-api/${VERSION}/" "gh-pages/api-doc/${VERSION}/" + bundle exec ruby tool/docs/publish_check.rb verify --pages gh-pages --snapshot tmp/gh-pages-api-doc-before.json --allow-version "${VERSION}" --expected-version "${VERSION}" - name: Commit changes as rmosolgo run: | git config --global user.name rmosolgo git config --global user.email rdmosolgo@gmail.com - git status - bundle exec rake site:commit_changes - git status + git -C gh-pages add -A + git -C gh-pages commit --allow-empty -m "Update API documentation" - name: Deploy to GitHub pages via gh-pages branch uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 with: diff --git a/.rdoc_options b/.rdoc_options new file mode 100644 index 00000000000..3e4ca6bb7ae --- /dev/null +++ b/.rdoc_options @@ -0,0 +1,4 @@ +--- +markup: markdown +main_page: readme.md +title: GraphQL Ruby API Documentation diff --git a/.yardopts b/.yardopts deleted file mode 100644 index 962de13a9af..00000000000 --- a/.yardopts +++ /dev/null @@ -1,5 +0,0 @@ ---no-private ---markup=markdown ---readme=readme.md ---title='GraphQL Ruby API Documentation' -'lib/**/*.rb' - '*.md' diff --git a/Gemfile b/Gemfile index 0a321a06a3c..8e57be9dc65 100644 --- a/Gemfile +++ b/Gemfile @@ -13,10 +13,6 @@ if RUBY_VERSION >= "3.2.0" gem "minitest-mock" end -# Website tasks opt in to these dependencies via BUNDLE_WITH=jekyll_plugins. -group :jekyll_plugins, optional: true do - gem 'jekyll' - gem 'jekyll-sass-converter', '~> 2.2' - gem 'jekyll-algolia', '~> 1.0' - gem 'jekyll-redirect-from' +group :docs, optional: true do + gem "rdoc", "~> 7.2" end diff --git a/Rakefile b/Rakefile index 4a48d826c40..c1128e59168 100644 --- a/Rakefile +++ b/Rakefile @@ -3,7 +3,6 @@ require "bundler/gem_helper" Bundler::GemHelper.install_tasks require "rake/testtask" -require_relative "guides/_tasks/site" require_relative "lib/graphql/rake_task/validate" require 'rake/extensiontask' @@ -19,8 +18,17 @@ Rake::TestTask.new do |t| end end + exclude_docs = begin + require "rdoc" + require "rdoc/generator/aliki" + false + rescue LoadError + true + end + t.test_files = FileList.new("spec/**/*_spec.rb") do |fl| fl.exclude(*exclude_integrations.map { |int| "spec/integration/#{int}/**/*" }) + fl.exclude("spec/docs/**/*") if exclude_docs end # After 2.7, there were not warnings for uninitialized ivars anymore @@ -236,5 +244,70 @@ task :move_binary do `mv graphql-c_parser/lib/*.bundle graphql-c_parser/lib/graphql` end +namespace :docs do + desc "Build the RDoc/Aliki documentation site" + task build: "docs:rdoc:build" + + desc "Build and run documentation quality checks" + task check: "docs:rdoc:build" do + ruby "tool/docs/check.rb" + ruby "tool/docs/type_signatures.rb", "--check" + ruby "tool/docs/migrate_guides.rb", "--check" + ruby "tool/docs/guide_audit.rb" + ruby "tool/docs/compatibility.rb", "--root", "tmp/rdoc-site", "--rdoc", "tmp/rdoc-site/js/search_data.js", "--baseline", "docs/yard_api_baseline.yml", "--strict", "--json", "tmp/rdoc-api-compatibility.json" + ruby "tool/docs/link_checker.rb", "--root", "tmp/rdoc-site", "--strict", "--json", "tmp/rdoc-link-report.json" + ruby "tool/docs/rdoc_ref_checker.rb", "--root", "tmp/rdoc-site", "--json", "tmp/rdoc-ref-report.json" + sh "node tool/docs/assets/graphql_highlighter_test.js" + end + + desc "Build the documentation twice and compare generated files" + task :build_twice do + require_relative "tool/docs/build" + require "digest" + require "fileutils" + require "pathname" + builder = GraphQLDocs::Build.new + first = builder.build_site(output: "tmp/rdoc-site-first") + second = builder.build_site(output: "tmp/rdoc-site-second") + digest = lambda do |root| + Dir[File.join(root, "**", "*")].select { |path| File.file?(path) }.sort.to_h do |path| + [Pathname.new(path).relative_path_from(Pathname.new(root)).to_s, Digest::SHA256.file(path).hexdigest] + end + end + raise "RDoc output is not reproducible" unless digest.call(first) == digest.call(second) + puts "RDoc output is reproducible" + ensure + FileUtils.rm_rf("tmp/rdoc-site-first") + FileUtils.rm_rf("tmp/rdoc-site-second") + end + + namespace :rdoc do + desc "Build the shadow RDoc/Aliki documentation site" + task :build do + require_relative "tool/docs/build" + GraphQLDocs::Build.new.build_site + end + + desc "Build versioned RDoc/Aliki API documentation" + task :build_version, [:version] do |_task, args| + require_relative "tool/docs/build" + version = args[:version] || ENV["GRAPHQL_VERSION"] || raise(ArgumentError, "A version is required") + GraphQLDocs::Build.new.build_version(version) + end + + desc "Build the shadow RDoc site and serve it locally" + task :serve => :build do + require "webrick" + server = WEBrick::HTTPServer.new( + Port: Integer(ENV.fetch("PORT", "8808")), + DocumentRoot: File.expand_path("tmp/rdoc-site"), + ) + trap("INT") { server.shutdown } + puts "Serving RDoc documentation at http://127.0.0.1:#{server.config[:Port]}" + server.start + end + end +end + desc "Build the C Extension" task build_ext: [:build_c_lexer, :build_yacc_parser, "compile:graphql_c_parser_ext", :move_binary] diff --git a/docs/guide_classification.yml b/docs/guide_classification.yml new file mode 100644 index 00000000000..2d9e93ad699 --- /dev/null +++ b/docs/guide_classification.yml @@ -0,0 +1,177 @@ +version: 1 + +# API-specific pages are owned by the implementation comments below. +api_comments: + - guide: guides/fields/introduction.md + source: lib/graphql/schema/field.rb + constant: GraphQL::Schema::Field + required_sections: + - "## Field Names" + - "## Field Return Type" + - "## Field Resolution" + - "## Field Parameter Default Values" + - guide: guides/fields/arguments.md + source: lib/graphql/schema/argument.rb + constant: GraphQL::Schema::Argument + required_sections: + - "## Nullability" + - "## Default Values" + - "## Valid Argument Types" + - guide: guides/schema/definition.md + source: lib/graphql/schema.rb + constant: GraphQL::Schema + kind: hybrid + rationale: Keep setup, lazy-loading, and production walkthroughs as a standalone tutorial while the schema configuration contracts live in GraphQL::Schema comments. + max_lines: 140 + required_sections: + - "Schema configuration reference" + - "## Root Types" + - "## Object Identification" + - "## Error Handling" + - "## Default Limits" + - "## Introspection" + - "## Authorization" + - "## Execution Configuration" + - "migrated from guides/schema/definition.md" + - guide: guides/queries/executing_queries.md + source: lib/graphql/query.rb + constant: GraphQL::Query + kind: hybrid + rationale: Keep variables, context, scoped context, and root-value walkthroughs as a tutorial while constructor and execution options are documented on the API methods. + max_lines: 220 + required_sections: + - "API-specific portions" + - "## API-specific portions" + - "`variables:` is a `Hash`" + - "migrated from guides/queries/executing_queries.md" + +# These files are generated API snapshots from the former YARD pipeline. +# They are excluded from the RDoc site; the Ruby source comments are canonical. +generated_api: + - guides/yardoc/**/*.md + +standalone_policy: Every remaining guide is reviewed as a tutorial, integration, or cross-cutting explanation. API contracts belong in implementation comments and are linked from these pages. + +# Every remaining Markdown guide is intentionally a standalone tutorial or +# cross-cutting explanation. Keep this list explicit so new pages are reviewed. +standalone: + - guides/authorization/authorization.md + - guides/authorization/can_can_integration.md + - guides/authorization/overview.md + - guides/authorization/pundit_integration.md + - guides/authorization/scoping.md + - guides/authorization/visibility.md + - guides/changesets/definition.md + - guides/changesets/installation.md + - guides/changesets/overview.md + - guides/changesets/releases.md + - guides/dataloader/adopting.md + - guides/dataloader/async_dataloader.md + - guides/dataloader/dataloader.md + - guides/dataloader/overview.md + - guides/dataloader/parallelism.md + - guides/dataloader/sources.md + - guides/dataloader/testing.md + - guides/defer/graphiql.md + - guides/defer/overview.md + - guides/defer/setup.md + - guides/defer/stream.md + - guides/defer/usage.md + - guides/development.md + - guides/errors/error_handling.md + - guides/errors/execution_errors.md + - guides/errors/overview.md + - guides/errors/type_errors.md + - guides/execution/migration.md + - guides/execution/next.md + - guides/faq.md + - guides/fields/limits.md + - guides/fields/resolvers.md + - guides/fields/validation.md + - guides/getting_started.md + - guides/javascript_client/apollo_subscriptions.md + - guides/javascript_client/graphiql_subscriptions.md + - guides/javascript_client/overview.md + - guides/javascript_client/relay_subscriptions.md + - guides/javascript_client/sync.md + - guides/javascript_client/urql_subscriptions.md + - guides/language_tools/c_parser.md + - guides/language_tools/visitor.md + - guides/limiters/active_operations.md + - guides/limiters/deployment.md + - guides/limiters/overview.md + - guides/limiters/redis.md + - guides/limiters/runtime.md + - guides/mutations/mutation_authorization.md + - guides/mutations/mutation_classes.md + - guides/mutations/mutation_errors.md + - guides/mutations/mutation_root.md + - guides/object_cache/caching.md + - guides/object_cache/memcached.md + - guides/object_cache/overview.md + - guides/object_cache/redis.md + - guides/object_cache/runtime_considerations.md + - guides/object_cache/schema_setup.md + - guides/operation_store/access_control.md + - guides/operation_store/active_record_backend.md + - guides/operation_store/client_workflow.md + - guides/operation_store/getting_started.md + - guides/operation_store/overview.md + - guides/operation_store/redis_backend.md + - guides/operation_store/server_management.md + - guides/pagination/connection_concepts.md + - guides/pagination/cursors.md + - guides/pagination/custom_connections.md + - guides/pagination/overview.md + - guides/pagination/stable_relation_connections.md + - guides/pagination/using_connections.md + - guides/pro/dashboard.md + - guides/pro/encoders.md + - guides/pro/home.md + - guides/pro/installation.md + - guides/pro/privacy.md + - guides/queries/ast_analysis.md + - guides/queries/backtrace_annotations.md + - guides/queries/complexity_and_depth.md + - guides/queries/logging.md + - guides/queries/lookahead.md + - guides/queries/multiplex.md + - guides/queries/phases_of_execution.md + - guides/queries/response_extensions.md + - guides/queries/timeout.md + - guides/queries/tracing.md + - guides/related_projects.md + - guides/relay/range_add.md + - guides/schema/dynamic_types.md + - guides/schema/generators.md + - guides/schema/introspection.md + - guides/schema/lazy_execution.md + - guides/schema/object_identification.md + - guides/schema/root_types.md + - guides/schema/sdl.md + - guides/subscriptions/ably_implementation.md + - guides/subscriptions/action_cable_implementation.md + - guides/subscriptions/broadcast.md + - guides/subscriptions/implementation.md + - guides/subscriptions/multi_tenant.md + - guides/subscriptions/overview.md + - guides/subscriptions/pusher_implementation.md + - guides/subscriptions/subscription_classes.md + - guides/subscriptions/subscription_type.md + - guides/subscriptions/triggers.md + - guides/testing/helpers.md + - guides/testing/integration_tests.md + - guides/testing/overview.md + - guides/testing/profiling.md + - guides/testing/schema_structure.md + - guides/type_definitions/directives.md + - guides/type_definitions/enums.md + - guides/type_definitions/extensions.md + - guides/type_definitions/field_extensions.md + - guides/type_definitions/input_objects.md + - guides/type_definitions/interfaces.md + - guides/type_definitions/lists.md + - guides/type_definitions/non_nulls.md + - guides/type_definitions/objects.md + - guides/type_definitions/scalars.md + - guides/type_definitions/unions.md diff --git a/docs/maintenance.md b/docs/maintenance.md new file mode 100644 index 00000000000..bac0cd19da0 --- /dev/null +++ b/docs/maintenance.md @@ -0,0 +1,35 @@ +# Documentation maintenance + +GraphQL-Ruby documentation is generated with RDoc and the Aliki generator. API comments live beside the Ruby code, while tutorials and cross-cutting explanations remain Markdown pages in `guides/`. + +Install the optional documentation dependencies and build a local site with: + +```sh +BUNDLE_WITH=docs bundle install +bundle exec rake docs:build +``` + +The generated site is written to `tmp/rdoc-site`. To preview it, run `bundle exec rake docs:rdoc:serve` and open `http://127.0.0.1:8808`. + +Run the documentation checks before submitting a change: + +```sh +bundle exec rake docs:check +bundle exec rake docs:build_twice +``` + +Use `rdoc-ref:GraphQL::Schema#execute` (or the corresponding class-method form) for links from comments and guides. Add a short API comment when a public method is missing from the generated index. Dynamic methods should use RDoc directives such as `:method:` and `:call-seq:` rather than introducing new YARD tags. + +Use a fenced `graphql` block for GraphQL examples: + +````markdown +```graphql +query Viewer { + viewer { id } +} +``` +```` + +When a page or API moves, add an entry to `docs/redirects.yml` so the old URL remains usable. Run `bundle exec ruby tool/docs/link_checker.rb --root tmp/rdoc-site --strict` to verify the redirect and its fragment. + +Release builds use `bundle exec rake "docs:rdoc:build_version[VERSION]"` to generate a versioned API site under `tmp/rdoc-api/VERSION`. The publish workflow copies that directory into `gh-pages/api-doc/VERSION` without removing older versions. diff --git a/docs/redirects.yml b/docs/redirects.yml new file mode 100644 index 00000000000..a3f530428b3 --- /dev/null +++ b/docs/redirects.yml @@ -0,0 +1,20 @@ +# Guide URLs are generated from the source tree so every existing guide has a +# concrete redirect. Add explicit entries for pages that move into an API ref. +guide_source: guides +redirects: + - old_path: /docs/maintenance + destination: + kind: page + value: docs/maintenance.md + - old_path: /guides + destination: + kind: page + value: guides/getting_started.md + - old_path: /queries/executing_queries + destination: + kind: rdoc_ref + value: GraphQL::Schema.execute + - old_path: /getting_started + destination: + kind: page + value: guides/getting_started.md diff --git a/docs/yard_api_baseline.yml b/docs/yard_api_baseline.yml new file mode 100644 index 00000000000..8e53676b1f2 --- /dev/null +++ b/docs/yard_api_baseline.yml @@ -0,0 +1,1177 @@ +version: 1 +source: YARD public API inventory generated before the RDoc migration (f1a619de08) +policy: Public YARD objects with documented comments; RDoc auto-generated constructors and dynamic visibility changes are explicitly reviewed below. +allowlist_review: + reviewed_at: "2026-08-11" + missing_reason: "YARD documented dynamic or nested objects whose RDoc representation is intentionally different, plus APIs explicitly marked private with :nodoc: during the migration; each entry is retained here so a future migration can review it explicitly." + extra_reason: RDoc-generated entries for constructors, inherited/dynamic APIs, or objects that YARD represented under a different kind; each entry is retained here so unexpected additions fail the check. +yard_api: + - ["class","GraphQL::Analysis::Analyzer"] + - ["class","GraphQL::Analysis::MaxQueryComplexity"] + - ["class","GraphQL::Analysis::QueryComplexity"] + - ["class","GraphQL::Analysis::QueryComplexity::ScopedTypeComplexity"] + - ["class","GraphQL::Analysis::QueryDepth"] + - ["class","GraphQL::Analysis::Visitor"] + - ["class","GraphQL::Backtrace"] + - ["class","GraphQL::Backtrace::Table"] + - ["class","GraphQL::Backtrace::TracedError"] + - ["class","GraphQL::Dataloader"] + - ["class","GraphQL::Dataloader::NullDataloader"] + - ["class","GraphQL::Dataloader::Request"] + - ["class","GraphQL::Dataloader::RequestAll"] + - ["class","GraphQL::DateEncodingError"] + - ["class","GraphQL::DurationEncodingError"] + - ["class","GraphQL::Execution::Interpreter::ArgumentValue"] + - ["class","GraphQL::Execution::Interpreter::Arguments"] + - ["class","GraphQL::Execution::Interpreter::RawValue"] + - ["class","GraphQL::Execution::Interpreter::Runtime"] + - ["class","GraphQL::Execution::Lazy"] + - ["class","GraphQL::Execution::Lazy::LazyMethodMap"] + - ["class","GraphQL::Execution::Lazy::LazyMethodMap::ConcurrentishMap"] + - ["class","GraphQL::Execution::Lookahead"] + - ["class","GraphQL::Execution::Lookahead::NullLookahead"] + - ["class","GraphQL::Execution::Multiplex"] + - ["class","GraphQL::Execution::Skip"] + - ["class","GraphQL::ExecutionError"] + - ["class","GraphQL::IntegerDecodingError"] + - ["class","GraphQL::IntegerEncodingError"] + - ["class","GraphQL::InvalidNullError"] + - ["class","GraphQL::InvariantError"] + - ["class","GraphQL::Language::Cache"] + - ["class","GraphQL::Language::DocumentFromSchemaDefinition"] + - ["class","GraphQL::Language::Nodes::AbstractNode"] + - ["class","GraphQL::Language::Nodes::Argument"] + - ["class","GraphQL::Language::Nodes::Document"] + - ["class","GraphQL::Language::Nodes::Enum"] + - ["class","GraphQL::Language::Nodes::Field"] + - ["class","GraphQL::Language::Nodes::FragmentDefinition"] + - ["class","GraphQL::Language::Nodes::FragmentSpread"] + - ["class","GraphQL::Language::Nodes::InlineFragment"] + - ["class","GraphQL::Language::Nodes::InputObject"] + - ["class","GraphQL::Language::Nodes::ListType"] + - ["class","GraphQL::Language::Nodes::NameOnlyNode"] + - ["class","GraphQL::Language::Nodes::NonNullType"] + - ["class","GraphQL::Language::Nodes::NullValue"] + - ["class","GraphQL::Language::Nodes::OperationDefinition"] + - ["class","GraphQL::Language::Nodes::TypeName"] + - ["class","GraphQL::Language::Nodes::VariableDefinition"] + - ["class","GraphQL::Language::Nodes::VariableIdentifier"] + - ["class","GraphQL::Language::Nodes::WrapperType"] + - ["class","GraphQL::Language::SanitizedPrinter"] + - ["class","GraphQL::Language::StaticVisitor"] + - ["class","GraphQL::Language::Visitor"] + - ["class","GraphQL::LoadApplicationObjectFailedError"] + - ["class","GraphQL::Pagination::ActiveRecordRelationConnection"] + - ["class","GraphQL::Pagination::Connection"] + - ["class","GraphQL::Pagination::Connection::Edge"] + - ["class","GraphQL::Pagination::Connections"] + - ["class","GraphQL::Pagination::RelationConnection"] + - ["class","GraphQL::Pagination::SequelDatasetConnection"] + - ["class","GraphQL::Query"] + - ["class","GraphQL::Query::Context"] + - ["class","GraphQL::Query::NullContext"] + - ["class","GraphQL::Query::Partial"] + - ["class","GraphQL::Query::Result"] + - ["class","GraphQL::Query::ValidationPipeline"] + - ["class","GraphQL::Query::Variables"] + - ["class","GraphQL::Railtie"] + - ["class","GraphQL::RakeTask"] + - ["class","GraphQL::Relay::RangeAdd"] + - ["class","GraphQL::Rubocop::GraphQL::DefaultNullTrue"] + - ["class","GraphQL::Rubocop::GraphQL::DefaultRequiredTrue"] + - ["class","GraphQL::Rubocop::GraphQL::FieldTypeInBlock"] + - ["class","GraphQL::Rubocop::GraphQL::RootTypesInBlock"] + - ["class","GraphQL::Schema"] + - ["class","GraphQL::Schema::Argument"] + - ["class","GraphQL::Schema::BuildFromDefinition::ResolveMap"] + - ["class","GraphQL::Schema::Directive"] + - ["class","GraphQL::Schema::Directive::Feature"] + - ["class","GraphQL::Schema::Directive::Flagged"] + - ["class","GraphQL::Schema::Directive::Transform"] + - ["class","GraphQL::Schema::Enum"] + - ["class","GraphQL::Schema::Enum::MissingValuesError"] + - ["class","GraphQL::Schema::Enum::UnresolvedValueError"] + - ["class","GraphQL::Schema::EnumValue"] + - ["class","GraphQL::Schema::Field"] + - ["class","GraphQL::Schema::FieldExtension"] + - ["class","GraphQL::Schema::Finder"] + - ["class","GraphQL::Schema::InputObject::ArgumentsAreRequiredError"] + - ["class","GraphQL::Schema::InvalidDocumentError"] + - ["class","GraphQL::Schema::LateBoundType"] + - ["class","GraphQL::Schema::List"] + - ["class","GraphQL::Schema::Member"] + - ["class","GraphQL::Schema::Mutation"] + - ["class","GraphQL::Schema::NonNull"] + - ["class","GraphQL::Schema::Object::FieldsAreRequiredError"] + - ["class","GraphQL::Schema::Printer"] + - ["class","GraphQL::Schema::RelayClassicMutation"] + - ["class","GraphQL::Schema::Resolver"] + - ["class","GraphQL::Schema::Subscription"] + - ["class","GraphQL::Schema::Timeout"] + - ["class","GraphQL::Schema::Timeout::TimeoutError"] + - ["class","GraphQL::Schema::TypeMembership"] + - ["class","GraphQL::Schema::Validator::AllValidator"] + - ["class","GraphQL::Schema::Validator::AllowBlankValidator"] + - ["class","GraphQL::Schema::Validator::AllowNullValidator"] + - ["class","GraphQL::Schema::Validator::ExclusionValidator"] + - ["class","GraphQL::Schema::Validator::FormatValidator"] + - ["class","GraphQL::Schema::Validator::InclusionValidator"] + - ["class","GraphQL::Schema::Validator::LengthValidator"] + - ["class","GraphQL::Schema::Validator::NumericalityValidator"] + - ["class","GraphQL::Schema::Validator::RequiredValidator"] + - ["class","GraphQL::Schema::Visibility"] + - ["class","GraphQL::Schema::Visibility::Migration"] + - ["class","GraphQL::Schema::Visibility::Profile"] + - ["class","GraphQL::Schema::Warden"] + - ["class","GraphQL::Schema::Warden::PassThruWarden"] + - ["class","GraphQL::StaticValidation::DefinitionDependencies::DependencyMap"] + - ["class","GraphQL::StaticValidation::Error"] + - ["class","GraphQL::StaticValidation::LiteralValidator"] + - ["class","GraphQL::StaticValidation::ValidationContext"] + - ["class","GraphQL::StaticValidation::Validator"] + - ["class","GraphQL::Subscriptions::ActionCableSubscriptions"] + - ["class","GraphQL::Subscriptions::BroadcastAnalyzer"] + - ["class","GraphQL::Subscriptions::Event"] + - ["class","GraphQL::Subscriptions::InvalidTriggerError"] + - ["class","GraphQL::Subscriptions::SubscriptionScopeMissingError"] + - ["class","GraphQL::Testing::MockActionCable"] + - ["class","GraphQL::Testing::MockActionCable::MockStream"] + - ["class","GraphQL::Tracing::AppOpticsTracing"] + - ["class","GraphQL::Tracing::DetailedTrace"] + - ["class","GraphQL::Tracing::DetailedTrace::MemoryBackend"] + - ["class","GraphQL::Tracing::NotificationsTrace::ActiveSupportNotificationsAdapter"] + - ["class","GraphQL::Tracing::NotificationsTrace::Adapter"] + - ["class","GraphQL::Tracing::NotificationsTrace::DryMonitorAdapter"] + - ["class","GraphQL::Tracing::NotificationsTracing"] + - ["class","GraphQL::Tracing::PlatformTracing"] + - ["class","GraphQL::Tracing::Trace"] + - ["class","GraphQL::TypeKinds::TypeKind"] + - ["class","GraphQL::Types::ISO8601Date"] + - ["class","GraphQL::Types::ISO8601DateTime"] + - ["class","GraphQL::Types::ISO8601Duration"] + - ["class","GraphQL::Types::Int"] + - ["class","GraphQL::Types::JSON"] + - ["class","GraphQL::Types::Relay::BaseConnection"] + - ["class","GraphQL::Types::Relay::BaseEdge"] + - ["class","GraphQL::Types::Relay::PageInfo"] + - ["class","GraphQL::UnauthorizedError"] + - ["class","GraphQL::UnresolvedTypeError"] + - ["class","Graphql::Dashboard"] + - ["class","Graphql::Generators::EnumGenerator"] + - ["class","Graphql::Generators::InputGenerator"] + - ["class","Graphql::Generators::InstallGenerator"] + - ["class","Graphql::Generators::InterfaceGenerator"] + - ["class","Graphql::Generators::LoaderGenerator"] + - ["class","Graphql::Generators::MutationCreateGenerator"] + - ["class","Graphql::Generators::MutationDeleteGenerator"] + - ["class","Graphql::Generators::MutationGenerator"] + - ["class","Graphql::Generators::MutationUpdateGenerator"] + - ["class","Graphql::Generators::ObjectGenerator"] + - ["class","Graphql::Generators::OrmMutationsBase"] + - ["class","Graphql::Generators::ScalarGenerator"] + - ["class","Graphql::Generators::UnionGenerator"] + - ["class_method","GraphQL.eager_load!"] + - ["class_method","GraphQL.parse"] + - ["class_method","GraphQL.parse_file"] + - ["class_method","GraphQL.scan"] + - ["class_method","GraphQL::Analysis.analyze_multiplex"] + - ["class_method","GraphQL::Analysis.analyze_query"] + - ["class_method","GraphQL::Current.dataloader_source"] + - ["class_method","GraphQL::Current.dataloader_source_class"] + - ["class_method","GraphQL::Current.field"] + - ["class_method","GraphQL::Current.operation_name"] + - ["class_method","GraphQL::Dataloader.with_dataloading"] + - ["class_method","GraphQL::Dataloader::Source.batch_key_for"] + - ["class_method","GraphQL::Execution::DirectiveChecks.include?"] + - ["class_method","GraphQL::Execution::Errors.find_handler_for"] + - ["class_method","GraphQL::Execution::Errors.register_rescue_from"] + - ["class_method","GraphQL::Execution::Interpreter.run_all"] + - ["class_method","GraphQL::Execution::Interpreter::Resolve.resolve"] + - ["class_method","GraphQL::Execution::Interpreter::Resolve.resolve_all"] + - ["class_method","GraphQL::Execution::Interpreter::Resolve.resolve_each_depth"] + - ["class_method","GraphQL::Execution::Lazy.all"] + - ["class_method","GraphQL::Introspection::InputValueType.serialize_default_value"] + - ["class_method","GraphQL::Language.escape_single_quoted_newlines"] + - ["class_method","GraphQL::Language.serialize"] + - ["class_method","GraphQL::Language::BlockString.trim_whitespace"] + - ["class_method","GraphQL::Language::Lexer.replace_escaped_characters_in_place"] + - ["class_method","GraphQL::Language::Lexer.tokenize"] + - ["class_method","GraphQL::Language::Nodes::AbstractNode.inherited"] + - ["class_method","GraphQL::Language::Nodes::Field.from_a"] + - ["class_method","GraphQL::Language::StaticVisitor.make_visit_methods"] + - ["class_method","GraphQL::Language::Visitor.make_visit_methods"] + - ["class_method","GraphQL::Query::Fingerprint.generate"] + - ["class_method","GraphQL::Schema.add_subscription_extension_if_necessary"] + - ["class_method","GraphQL::Schema.after_any_lazies"] + - ["class_method","GraphQL::Schema.after_lazy"] + - ["class_method","GraphQL::Schema.allow_legacy_invalid_empty_selections_on_union"] + - ["class_method","GraphQL::Schema.allow_legacy_invalid_return_type_conflicts"] + - ["class_method","GraphQL::Schema.as_json"] + - ["class_method","GraphQL::Schema.complexity_cost_calculation_mode"] + - ["class_method","GraphQL::Schema.complexity_cost_calculation_mode_for"] + - ["class_method","GraphQL::Schema.context_class"] + - ["class_method","GraphQL::Schema.default_logger"] + - ["class_method","GraphQL::Schema.default_trace_mode"] + - ["class_method","GraphQL::Schema.description"] + - ["class_method","GraphQL::Schema.detailed_trace?"] + - ["class_method","GraphQL::Schema.did_you_mean"] + - ["class_method","GraphQL::Schema.directive"] + - ["class_method","GraphQL::Schema.directives"] + - ["class_method","GraphQL::Schema.execute"] + - ["class_method","GraphQL::Schema.extra_types"] + - ["class_method","GraphQL::Schema.from_definition"] + - ["class_method","GraphQL::Schema.from_introspection"] + - ["class_method","GraphQL::Schema.get_type"] + - ["class_method","GraphQL::Schema.handle_or_reraise"] + - ["class_method","GraphQL::Schema.has_defined_type?"] + - ["class_method","GraphQL::Schema.id_from_object"] + - ["class_method","GraphQL::Schema.inherited"] + - ["class_method","GraphQL::Schema.introspection"] + - ["class_method","GraphQL::Schema.introspection_system"] + - ["class_method","GraphQL::Schema.lazy?"] + - ["class_method","GraphQL::Schema.lazy_method_name"] + - ["class_method","GraphQL::Schema.legacy_complexity_cost_calculation_mismatch"] + - ["class_method","GraphQL::Schema.legacy_invalid_empty_selections_on_union"] + - ["class_method","GraphQL::Schema.legacy_invalid_empty_selections_on_union_with_type"] + - ["class_method","GraphQL::Schema.legacy_invalid_return_type_conflicts"] + - ["class_method","GraphQL::Schema.load_type"] + - ["class_method","GraphQL::Schema.logger_for"] + - ["class_method","GraphQL::Schema.max_query_string_tokens"] + - ["class_method","GraphQL::Schema.multiplex"] + - ["class_method","GraphQL::Schema.multiplex_analyzer"] + - ["class_method","GraphQL::Schema.mutation"] + - ["class_method","GraphQL::Schema.new_trace"] + - ["class_method","GraphQL::Schema.object_from_id"] + - ["class_method","GraphQL::Schema.orphan_types"] + - ["class_method","GraphQL::Schema.parse_error"] + - ["class_method","GraphQL::Schema.possible_types"] + - ["class_method","GraphQL::Schema.query"] + - ["class_method","GraphQL::Schema.query_analyzer"] + - ["class_method","GraphQL::Schema.query_class"] + - ["class_method","GraphQL::Schema.query_stack_error"] + - ["class_method","GraphQL::Schema.rescue_from"] + - ["class_method","GraphQL::Schema.resolve_type"] + - ["class_method","GraphQL::Schema.root_type_for_operation"] + - ["class_method","GraphQL::Schema.root_types"] + - ["class_method","GraphQL::Schema.subscription"] + - ["class_method","GraphQL::Schema.subscriptions"] + - ["class_method","GraphQL::Schema.sync_lazy"] + - ["class_method","GraphQL::Schema.to_definition"] + - ["class_method","GraphQL::Schema.to_document"] + - ["class_method","GraphQL::Schema.to_json"] + - ["class_method","GraphQL::Schema.trace_class_for"] + - ["class_method","GraphQL::Schema.trace_mode"] + - ["class_method","GraphQL::Schema.trace_modules_for"] + - ["class_method","GraphQL::Schema.trace_options_for"] + - ["class_method","GraphQL::Schema.trace_with"] + - ["class_method","GraphQL::Schema.type_error"] + - ["class_method","GraphQL::Schema.types"] + - ["class_method","GraphQL::Schema.unauthorized_field"] + - ["class_method","GraphQL::Schema.unauthorized_object"] + - ["class_method","GraphQL::Schema.use"] + - ["class_method","GraphQL::Schema.use_visibility_profile?"] + - ["class_method","GraphQL::Schema.validate"] + - ["class_method","GraphQL::Schema::BuildFromDefinition.from_definition"] + - ["class_method","GraphQL::Schema::Directive.default_graphql_name"] + - ["class_method","GraphQL::Schema::Directive.include?"] + - ["class_method","GraphQL::Schema::Directive.resolve"] + - ["class_method","GraphQL::Schema::Directive.resolve_each"] + - ["class_method","GraphQL::Schema::Directive.static_include?"] + - ["class_method","GraphQL::Schema::Directive::Feature.enabled?"] + - ["class_method","GraphQL::Schema::Directive::Feature.include?"] + - ["class_method","GraphQL::Schema::Directive::Transform.resolve"] + - ["class_method","GraphQL::Schema::Enum.all_enum_value_definitions"] + - ["class_method","GraphQL::Schema::Enum.coerce_input"] + - ["class_method","GraphQL::Schema::Enum.coerce_result"] + - ["class_method","GraphQL::Schema::Enum.enum_value_class"] + - ["class_method","GraphQL::Schema::Enum.enum_values"] + - ["class_method","GraphQL::Schema::Enum.value"] + - ["class_method","GraphQL::Schema::Enum.values"] + - ["class_method","GraphQL::Schema::Field.connection_extension"] + - ["class_method","GraphQL::Schema::FieldExtension.default_argument"] + - ["class_method","GraphQL::Schema::FieldExtension.default_argument_configurations"] + - ["class_method","GraphQL::Schema::FieldExtension.extras"] + - ["class_method","GraphQL::Schema::InputObject.coerce_result"] + - ["class_method","GraphQL::Schema::InputObject.has_no_arguments"] + - ["class_method","GraphQL::Schema::InputObject.has_no_arguments?"] + - ["class_method","GraphQL::Schema::Member::BuildType.constantize"] + - ["class_method","GraphQL::Schema::Member::BuildType.parse_type"] + - ["class_method","GraphQL::Schema::Object.authorized_new"] + - ["class_method","GraphQL::Schema::Object.const_missing"] + - ["class_method","GraphQL::Schema::Object.wrap"] + - ["class_method","GraphQL::Schema::Printer.print_introspection_schema"] + - ["class_method","GraphQL::Schema::Printer.print_schema"] + - ["class_method","GraphQL::Schema::Resolver.argument"] + - ["class_method","GraphQL::Schema::Resolver.broadcastable?"] + - ["class_method","GraphQL::Schema::Resolver.complexity"] + - ["class_method","GraphQL::Schema::Resolver.default_page_size"] + - ["class_method","GraphQL::Schema::Resolver.extension"] + - ["class_method","GraphQL::Schema::Resolver.extensions"] + - ["class_method","GraphQL::Schema::Resolver.extras"] + - ["class_method","GraphQL::Schema::Resolver.has_default_page_size?"] + - ["class_method","GraphQL::Schema::Resolver.has_max_page_size?"] + - ["class_method","GraphQL::Schema::Resolver.max_page_size"] + - ["class_method","GraphQL::Schema::Resolver.null"] + - ["class_method","GraphQL::Schema::Resolver.resolve_method"] + - ["class_method","GraphQL::Schema::Resolver.type"] + - ["class_method","GraphQL::Schema::Resolver.type_expr"] + - ["class_method","GraphQL::Schema::Subscription.subscription_scope"] + - ["class_method","GraphQL::Schema::Subscription.topic_for"] + - ["class_method","GraphQL::Schema::TypeExpression.build_type"] + - ["class_method","GraphQL::Schema::Union.assign_type_membership_object_type"] + - ["class_method","GraphQL::Schema::UniqueWithinType.decode"] + - ["class_method","GraphQL::Schema::UniqueWithinType.encode"] + - ["class_method","GraphQL::Schema::Validator.from_config"] + - ["class_method","GraphQL::Schema::Validator.install"] + - ["class_method","GraphQL::Schema::Validator.uninstall"] + - ["class_method","GraphQL::Schema::Validator.validate!"] + - ["class_method","GraphQL::Schema::Visibility.use"] + - ["class_method","GraphQL::Schema::Visibility::Profile.from_context"] + - ["class_method","GraphQL::Schema::Warden.visible_entry?"] + - ["class_method","GraphQL::StaticValidation::BaseVisitor.including_rules"] + - ["class_method","GraphQL::Subscriptions.use"] + - ["class_method","GraphQL::Subscriptions::Event.serialize"] + - ["class_method","GraphQL::Subscriptions::Serialize.dump"] + - ["class_method","GraphQL::Subscriptions::Serialize.dump_recursive"] + - ["class_method","GraphQL::Subscriptions::Serialize.load"] + - ["class_method","GraphQL::Testing::Helpers.for"] + - ["class_method","GraphQL::Testing::MockActionCable.broadcast"] + - ["class_method","GraphQL::Testing::MockActionCable.clear_mocks"] + - ["class_method","GraphQL::Testing::MockActionCable.get_mock_channel"] + - ["class_method","GraphQL::Testing::MockActionCable.mock_stream_for"] + - ["class_method","GraphQL::Testing::MockActionCable.mock_stream_names"] + - ["class_method","GraphQL::Testing::MockActionCable.server"] + - ["class_method","GraphQL::Tracing::AppOpticsTracing.version"] + - ["class_method","GraphQL::Tracing::DetailedTrace.debug?"] + - ["class_method","GraphQL::Tracing::DetailedTrace.use"] + - ["class_method","GraphQL::Tracing::LegacyHooksTrace::RunHooks.call_hooks"] + - ["class_method","GraphQL::Tracing::LegacyHooksTrace::RunHooks.each_query_call_hooks"] + - ["class_method","GraphQL::Types::ISO8601Date.coerce_input"] + - ["class_method","GraphQL::Types::ISO8601Date.coerce_result"] + - ["class_method","GraphQL::Types::ISO8601DateTime.coerce_input"] + - ["class_method","GraphQL::Types::ISO8601DateTime.coerce_result"] + - ["class_method","GraphQL::Types::ISO8601DateTime.time_precision"] + - ["class_method","GraphQL::Types::ISO8601DateTime.time_precision="] + - ["class_method","GraphQL::Types::ISO8601Duration.coerce_input"] + - ["class_method","GraphQL::Types::ISO8601Duration.coerce_result"] + - ["class_method","GraphQL::Types::ISO8601Duration.seconds_precision"] + - ["class_method","GraphQL::Types::ISO8601Duration.seconds_precision="] + - ["class_method","Graphql::Generators::TypeGeneratorBase.normalize_type_expression"] + - ["instance_method","GraphQL::Analysis::Analyzer#analyze?"] + - ["instance_method","GraphQL::Analysis::Analyzer#result"] + - ["instance_method","GraphQL::Analysis::Analyzer#visit?"] + - ["instance_method","GraphQL::Analysis::QueryComplexity#initialize"] + - ["instance_method","GraphQL::Analysis::QueryComplexity#result"] + - ["instance_method","GraphQL::Analysis::QueryComplexity::ScopedTypeComplexity#initialize"] + - ["instance_method","GraphQL::Analysis::Visitor#argument_definition"] + - ["instance_method","GraphQL::Analysis::Visitor#arguments_for"] + - ["instance_method","GraphQL::Analysis::Visitor#directive_definition"] + - ["instance_method","GraphQL::Analysis::Visitor#field_definition"] + - ["instance_method","GraphQL::Analysis::Visitor#on_operation_definition"] + - ["instance_method","GraphQL::Analysis::Visitor#parent_type_definition"] + - ["instance_method","GraphQL::Analysis::Visitor#previous_argument_definition"] + - ["instance_method","GraphQL::Analysis::Visitor#previous_field_definition"] + - ["instance_method","GraphQL::Analysis::Visitor#response_path"] + - ["instance_method","GraphQL::Analysis::Visitor#skipping?"] + - ["instance_method","GraphQL::Analysis::Visitor#type_definition"] + - ["instance_method","GraphQL::Analysis::Visitor#visiting_fragment_definition?"] + - ["instance_method","GraphQL::Autoload#autoload"] + - ["instance_method","GraphQL::Autoload#eager_load!"] + - ["instance_method","GraphQL::Backtrace::Table#to_backtrace"] + - ["instance_method","GraphQL::Backtrace::Table#to_table"] + - ["instance_method","GraphQL::Dataloader#append_job"] + - ["instance_method","GraphQL::Dataloader#cleanup_fiber"] + - ["instance_method","GraphQL::Dataloader#clear_cache"] + - ["instance_method","GraphQL::Dataloader#get_fiber_variables"] + - ["instance_method","GraphQL::Dataloader#lazy_at_depth"] + - ["instance_method","GraphQL::Dataloader#merge_records"] + - ["instance_method","GraphQL::Dataloader#queue_pending_source"] + - ["instance_method","GraphQL::Dataloader#run"] + - ["instance_method","GraphQL::Dataloader#run_isolated"] + - ["instance_method","GraphQL::Dataloader#set_fiber_variables"] + - ["instance_method","GraphQL::Dataloader#yield"] + - ["instance_method","GraphQL::Dataloader::AsyncDataloader::Run#expect_resumes"] + - ["instance_method","GraphQL::Dataloader::Request#load"] + - ["instance_method","GraphQL::Dataloader::RequestAll#load"] + - ["instance_method","GraphQL::Dataloader::Source#clear_cache"] + - ["instance_method","GraphQL::Dataloader::Source#fetch"] + - ["instance_method","GraphQL::Dataloader::Source#load"] + - ["instance_method","GraphQL::Dataloader::Source#load_all"] + - ["instance_method","GraphQL::Dataloader::Source#merge"] + - ["instance_method","GraphQL::Dataloader::Source#normalize_fetch_key"] + - ["instance_method","GraphQL::Dataloader::Source#pending?"] + - ["instance_method","GraphQL::Dataloader::Source#request"] + - ["instance_method","GraphQL::Dataloader::Source#request_all"] + - ["instance_method","GraphQL::Dataloader::Source#result_key_for"] + - ["instance_method","GraphQL::Dataloader::Source#run_pending_keys"] + - ["instance_method","GraphQL::Dataloader::Source#setup"] + - ["instance_method","GraphQL::Dataloader::Source#sync"] + - ["instance_method","GraphQL::Dig#dig"] + - ["instance_method","GraphQL::Execution::FieldResolveStep#arguments_without_loads"] + - ["instance_method","GraphQL::Execution::Interpreter::ArgumentValue#default_used?"] + - ["instance_method","GraphQL::Execution::Interpreter::Arguments#initialize"] + - ["instance_method","GraphQL::Execution::Interpreter::Arguments#merge_extras"] + - ["instance_method","GraphQL::Execution::Interpreter::ArgumentsCache#dataload_for"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#after_lazy"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#continue_field"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#continue_value"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#directives_include?"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#evaluate_selection"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#evaluate_selection_with_args"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#evaluate_selection_with_resolved_keyword_args"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#evaluate_selections"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#resolve_list_item"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#run_eager"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#set_graphql_dead"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime::GraphQLResult#initialize"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime::GraphQLResultArray#initialize"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime::GraphQLResultHash#collect_result"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime::GraphQLResultHash#initialize"] + - ["instance_method","GraphQL::Execution::Lazy#initialize"] + - ["instance_method","GraphQL::Execution::Lazy#then"] + - ["instance_method","GraphQL::Execution::Lazy#value"] + - ["instance_method","GraphQL::Execution::Lazy::LazyMethodMap#get"] + - ["instance_method","GraphQL::Execution::Lazy::LazyMethodMap#set"] + - ["instance_method","GraphQL::Execution::Lookahead#alias_selection"] + - ["instance_method","GraphQL::Execution::Lookahead#arguments"] + - ["instance_method","GraphQL::Execution::Lookahead#initialize"] + - ["instance_method","GraphQL::Execution::Lookahead#name"] + - ["instance_method","GraphQL::Execution::Lookahead#selected?"] + - ["instance_method","GraphQL::Execution::Lookahead#selection"] + - ["instance_method","GraphQL::Execution::Lookahead#selections"] + - ["instance_method","GraphQL::Execution::Lookahead#selects?"] + - ["instance_method","GraphQL::Execution::Lookahead#selects_alias?"] + - ["instance_method","GraphQL::Execution::Lookahead::NullLookahead#initialize"] + - ["instance_method","GraphQL::Execution::Runner#add_finalizer"] + - ["instance_method","GraphQL::ExecutionError#to_h"] + - ["instance_method","GraphQL::InvalidNullError#ast_node"] + - ["instance_method","GraphQL::Language::Generation#generate"] + - ["instance_method","GraphQL::Language::Lexer#_hash"] + - ["instance_method","GraphQL::Language::Nodes::AbstractNode#=="] + - ["instance_method","GraphQL::Language::Nodes::AbstractNode#children"] + - ["instance_method","GraphQL::Language::Nodes::AbstractNode#delete_child"] + - ["instance_method","GraphQL::Language::Nodes::AbstractNode#initialize_copy"] + - ["instance_method","GraphQL::Language::Nodes::AbstractNode#merge"] + - ["instance_method","GraphQL::Language::Nodes::AbstractNode#replace_child"] + - ["instance_method","GraphQL::Language::Nodes::AbstractNode#scalars"] + - ["instance_method","GraphQL::Language::Nodes::Argument#children"] + - ["instance_method","GraphQL::Language::Nodes::Document#slice_definition"] + - ["instance_method","GraphQL::Language::Nodes::FieldDefinition#fields"] + - ["instance_method","GraphQL::Language::Nodes::InputObject#to_h"] + - ["instance_method","GraphQL::Language::Printer#print"] + - ["instance_method","GraphQL::Language::SanitizedPrinter#print_operation_definition"] + - ["instance_method","GraphQL::Language::SanitizedPrinter#redact_argument_value?"] + - ["instance_method","GraphQL::Language::SanitizedPrinter#redacted_argument_value"] + - ["instance_method","GraphQL::Language::SanitizedPrinter#sanitized_query_string"] + - ["instance_method","GraphQL::Language::StaticVisitor#visit"] + - ["instance_method","GraphQL::Language::Visitor#visit"] + - ["instance_method","GraphQL::Pagination::Connection#after"] + - ["instance_method","GraphQL::Pagination::Connection#before"] + - ["instance_method","GraphQL::Pagination::Connection#cursor_for"] + - ["instance_method","GraphQL::Pagination::Connection#edge_nodes"] + - ["instance_method","GraphQL::Pagination::Connection#edges"] + - ["instance_method","GraphQL::Pagination::Connection#end_cursor"] + - ["instance_method","GraphQL::Pagination::Connection#has_next_page"] + - ["instance_method","GraphQL::Pagination::Connection#has_previous_page"] + - ["instance_method","GraphQL::Pagination::Connection#initialize"] + - ["instance_method","GraphQL::Pagination::Connection#nodes"] + - ["instance_method","GraphQL::Pagination::Connection#page_info"] + - ["instance_method","GraphQL::Pagination::Connection#range_add_edge"] + - ["instance_method","GraphQL::Pagination::Connection#start_cursor"] + - ["instance_method","GraphQL::Pagination::Connections#edge_class_for_field"] + - ["instance_method","GraphQL::Pagination::Connections#wrap"] + - ["instance_method","GraphQL::Query#current_trace"] + - ["instance_method","GraphQL::Query#document"] + - ["instance_method","GraphQL::Query#fingerprint"] + - ["instance_method","GraphQL::Query#initialize"] + - ["instance_method","GraphQL::Query#lookahead"] + - ["instance_method","GraphQL::Query#operation_fingerprint"] + - ["instance_method","GraphQL::Query#resolve_type"] + - ["instance_method","GraphQL::Query#result"] + - ["instance_method","GraphQL::Query#run_partials"] + - ["instance_method","GraphQL::Query#sanitized_query_string"] + - ["instance_method","GraphQL::Query#selected_operation"] + - ["instance_method","GraphQL::Query#selected_operation_name"] + - ["instance_method","GraphQL::Query#variables"] + - ["instance_method","GraphQL::Query#variables_fingerprint"] + - ["instance_method","GraphQL::Query::Context#[]"] + - ["instance_method","GraphQL::Query::Context#add_error"] + - ["instance_method","GraphQL::Query::Context#backtrace"] + - ["instance_method","GraphQL::Query::Context#initialize"] + - ["instance_method","GraphQL::Query::Context#namespace"] + - ["instance_method","GraphQL::Query::Context#namespace?"] + - ["instance_method","GraphQL::Query::Context#raw_value"] + - ["instance_method","GraphQL::Query::Context#response_extensions"] + - ["instance_method","GraphQL::Query::Context#scoped"] + - ["instance_method","GraphQL::Query::Context#skip"] + - ["instance_method","GraphQL::Query::Partial#initialize"] + - ["instance_method","GraphQL::Query::Partial::Result#partial"] + - ["instance_method","GraphQL::Query::Result#=="] + - ["instance_method","GraphQL::Query::Result#method_missing"] + - ["instance_method","GraphQL::Query::Runnable#arguments_for"] + - ["instance_method","GraphQL::Query::Runnable#handle_or_reraise"] + - ["instance_method","GraphQL::Query::ValidationPipeline#valid?"] + - ["instance_method","GraphQL::Query::ValidationPipeline#validation_errors"] + - ["instance_method","GraphQL::RakeTask#initialize"] + - ["instance_method","GraphQL::Relay::RangeAdd#initialize"] + - ["instance_method","GraphQL::Rubocop::GraphQL::BaseCop#source_without_keyword_argument"] + - ["instance_method","GraphQL::Schema::Argument#coerce_into_values"] + - ["instance_method","GraphQL::Schema::Argument#default_value"] + - ["instance_method","GraphQL::Schema::Argument#default_value?"] + - ["instance_method","GraphQL::Schema::Argument#deprecation_reason"] + - ["instance_method","GraphQL::Schema::Argument#from_resolver?"] + - ["instance_method","GraphQL::Schema::Argument#graphql_name"] + - ["instance_method","GraphQL::Schema::Argument#initialize"] + - ["instance_method","GraphQL::Schema::Argument#prepare"] + - ["instance_method","GraphQL::Schema::Argument#prepare_value"] + - ["instance_method","GraphQL::Schema::Argument#validate_default_value"] + - ["instance_method","GraphQL::Schema::BuildFromDefinition::Builder#replace_late_bound_types_with_built_in"] + - ["instance_method","GraphQL::Schema::BuildFromDefinition::ResolveMap::DefaultResolve#call"] + - ["instance_method","GraphQL::Schema::Field#broadcastable?"] + - ["instance_method","GraphQL::Schema::Field#comment"] + - ["instance_method","GraphQL::Schema::Field#connection?"] + - ["instance_method","GraphQL::Schema::Field#default_page_size"] + - ["instance_method","GraphQL::Schema::Field#deprecation_reason"] + - ["instance_method","GraphQL::Schema::Field#ensure_loaded"] + - ["instance_method","GraphQL::Schema::Field#extension"] + - ["instance_method","GraphQL::Schema::Field#extensions"] + - ["instance_method","GraphQL::Schema::Field#extras"] + - ["instance_method","GraphQL::Schema::Field#fetch_extra"] + - ["instance_method","GraphQL::Schema::Field#graphql_name"] + - ["instance_method","GraphQL::Schema::Field#has_default_page_size?"] + - ["instance_method","GraphQL::Schema::Field#has_max_page_size?"] + - ["instance_method","GraphQL::Schema::Field#initialize"] + - ["instance_method","GraphQL::Schema::Field#introspection?"] + - ["instance_method","GraphQL::Schema::Field#max_page_size"] + - ["instance_method","GraphQL::Schema::Field#method_conflict_warning?"] + - ["instance_method","GraphQL::Schema::Field#mutation"] + - ["instance_method","GraphQL::Schema::Field#owner_type"] + - ["instance_method","GraphQL::Schema::Field#resolve"] + - ["instance_method","GraphQL::Schema::Field#resolver"] + - ["instance_method","GraphQL::Schema::Field#resolver_method"] + - ["instance_method","GraphQL::Schema::Field#scoped?"] + - ["instance_method","GraphQL::Schema::Field::ConnectionExtension#resolve"] + - ["instance_method","GraphQL::Schema::FieldExtension#after_define"] + - ["instance_method","GraphQL::Schema::FieldExtension#after_define_apply"] + - ["instance_method","GraphQL::Schema::FieldExtension#after_resolve"] + - ["instance_method","GraphQL::Schema::FieldExtension#apply"] + - ["instance_method","GraphQL::Schema::FieldExtension#initialize"] + - ["instance_method","GraphQL::Schema::FieldExtension#resolve"] + - ["instance_method","GraphQL::Schema::HasSingleInputArgument::ClassMethods#argument"] + - ["instance_method","GraphQL::Schema::HasSingleInputArgument::ClassMethods#input_object_class"] + - ["instance_method","GraphQL::Schema::HasSingleInputArgument::ClassMethods#input_type"] + - ["instance_method","GraphQL::Schema::InputObject#[]"] + - ["instance_method","GraphQL::Schema::InputObject#to_kwargs"] + - ["instance_method","GraphQL::Schema::InputObject#validate_for"] + - ["instance_method","GraphQL::Schema::Interface::DefinitionMethods#definition_methods"] + - ["instance_method","GraphQL::Schema::Interface::DefinitionMethods#included"] + - ["instance_method","GraphQL::Schema::Interface::DefinitionMethods#orphan_types"] + - ["instance_method","GraphQL::Schema::Interface::DefinitionMethods#resolver_methods"] + - ["instance_method","GraphQL::Schema::Interface::DefinitionMethods#visible?"] + - ["instance_method","GraphQL::Schema::IntrospectionSystem#resolve_late_bindings"] + - ["instance_method","GraphQL::Schema::LateBoundType#graphql_name"] + - ["instance_method","GraphQL::Schema::List#description"] + - ["instance_method","GraphQL::Schema::List#graphql_name"] + - ["instance_method","GraphQL::Schema::List#kind"] + - ["instance_method","GraphQL::Schema::List#list?"] + - ["instance_method","GraphQL::Schema::Loader#load"] + - ["instance_method","GraphQL::Schema::Member::BaseDSLMethods#comment"] + - ["instance_method","GraphQL::Schema::Member::BaseDSLMethods#description"] + - ["instance_method","GraphQL::Schema::Member::BaseDSLMethods#introspection"] + - ["instance_method","GraphQL::Schema::Member::BaseDSLMethods#mutation"] + - ["instance_method","GraphQL::Schema::Member::HasArguments#add_argument"] + - ["instance_method","GraphQL::Schema::Member::HasArguments#argument"] + - ["instance_method","GraphQL::Schema::Member::HasArguments#argument_class"] + - ["instance_method","GraphQL::Schema::Member::HasArguments#arguments"] + - ["instance_method","GraphQL::Schema::Member::HasArguments#coerce_arguments"] + - ["instance_method","GraphQL::Schema::Member::HasArguments#get_argument"] + - ["instance_method","GraphQL::Schema::Member::HasArguments#validate_directive_argument"] + - ["instance_method","GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader#load_application_object_failed"] + - ["instance_method","GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader#object_from_id"] + - ["instance_method","GraphQL::Schema::Member::HasDataloader#dataload"] + - ["instance_method","GraphQL::Schema::Member::HasDataloader#dataload_all"] + - ["instance_method","GraphQL::Schema::Member::HasDataloader#dataload_all_associations"] + - ["instance_method","GraphQL::Schema::Member::HasDataloader#dataload_all_records"] + - ["instance_method","GraphQL::Schema::Member::HasDataloader#dataload_association"] + - ["instance_method","GraphQL::Schema::Member::HasDataloader#dataload_record"] + - ["instance_method","GraphQL::Schema::Member::HasDataloader#dataloader"] + - ["instance_method","GraphQL::Schema::Member::HasDirectives#directive"] + - ["instance_method","GraphQL::Schema::Member::HasDirectives#remove_directive"] + - ["instance_method","GraphQL::Schema::Member::HasFields#add_field"] + - ["instance_method","GraphQL::Schema::Member::HasFields#field"] + - ["instance_method","GraphQL::Schema::Member::HasFields#field_class"] + - ["instance_method","GraphQL::Schema::Member::HasFields#has_no_fields"] + - ["instance_method","GraphQL::Schema::Member::HasFields#has_no_fields?"] + - ["instance_method","GraphQL::Schema::Member::HasFields#own_fields"] + - ["instance_method","GraphQL::Schema::Member::HasFields::InterfaceMethods#fields"] + - ["instance_method","GraphQL::Schema::Member::HasFields::ObjectMethods#fields"] + - ["instance_method","GraphQL::Schema::Member::HasInterfaces#interfaces"] + - ["instance_method","GraphQL::Schema::Member::HasInterfaces::ClassConfigured#inherited"] + - ["instance_method","GraphQL::Schema::Member::HasPath#path"] + - ["instance_method","GraphQL::Schema::Member::HasValidators#validates"] + - ["instance_method","GraphQL::Schema::Member::HasValidators#validators"] + - ["instance_method","GraphQL::Schema::Member::Scoped#scope_items"] + - ["instance_method","GraphQL::Schema::Member::TypeSystemHelpers#kind"] + - ["instance_method","GraphQL::Schema::Member::TypeSystemHelpers#list?"] + - ["instance_method","GraphQL::Schema::Member::TypeSystemHelpers#non_null?"] + - ["instance_method","GraphQL::Schema::Member::TypeSystemHelpers#to_list_type"] + - ["instance_method","GraphQL::Schema::Member::TypeSystemHelpers#to_non_null_type"] + - ["instance_method","GraphQL::Schema::Mutation#call_resolve"] + - ["instance_method","GraphQL::Schema::NonNull#description"] + - ["instance_method","GraphQL::Schema::NonNull#graphql_name"] + - ["instance_method","GraphQL::Schema::NonNull#kind"] + - ["instance_method","GraphQL::Schema::NonNull#list?"] + - ["instance_method","GraphQL::Schema::NonNull#non_null?"] + - ["instance_method","GraphQL::Schema::Object#dataloader"] + - ["instance_method","GraphQL::Schema::Object#raw_value"] + - ["instance_method","GraphQL::Schema::Printer#initialize"] + - ["instance_method","GraphQL::Schema::Printer#print_schema"] + - ["instance_method","GraphQL::Schema::RactorShareable::SchemaExtension::FrozenMethods#lazy?"] + - ["instance_method","GraphQL::Schema::RelayClassicMutation#resolve_with_support"] + - ["instance_method","GraphQL::Schema::Resolver#authorized?"] + - ["instance_method","GraphQL::Schema::Resolver#call_resolve"] + - ["instance_method","GraphQL::Schema::Resolver#initialize"] + - ["instance_method","GraphQL::Schema::Resolver#ready?"] + - ["instance_method","GraphQL::Schema::Resolver#resolve"] + - ["instance_method","GraphQL::Schema::Resolver#resolve_with_support"] + - ["instance_method","GraphQL::Schema::Resolver#unauthorized_object"] + - ["instance_method","GraphQL::Schema::Resolver::HasPayloadType#object_class"] + - ["instance_method","GraphQL::Schema::Resolver::HasPayloadType#payload_type"] + - ["instance_method","GraphQL::Schema::Resolver::HasPayloadType#type_expr"] + - ["instance_method","GraphQL::Schema::Subscription#call_resolve"] + - ["instance_method","GraphQL::Schema::Subscription#event"] + - ["instance_method","GraphQL::Schema::Subscription#initialize"] + - ["instance_method","GraphQL::Schema::Subscription#load_application_object_failed"] + - ["instance_method","GraphQL::Schema::Subscription#resolve"] + - ["instance_method","GraphQL::Schema::Subscription#resolve_subscribe"] + - ["instance_method","GraphQL::Schema::Subscription#resolve_update"] + - ["instance_method","GraphQL::Schema::Subscription#resolve_with_support"] + - ["instance_method","GraphQL::Schema::Subscription#subscribe"] + - ["instance_method","GraphQL::Schema::Subscription#subscription_written?"] + - ["instance_method","GraphQL::Schema::Subscription#unsubscribe"] + - ["instance_method","GraphQL::Schema::Subscription#update"] + - ["instance_method","GraphQL::Schema::Subscription#write_subscription"] + - ["instance_method","GraphQL::Schema::Timeout#disable_timeout"] + - ["instance_method","GraphQL::Schema::Timeout#handle_timeout"] + - ["instance_method","GraphQL::Schema::Timeout#max_seconds"] + - ["instance_method","GraphQL::Schema::Timeout::Trace#initialize"] + - ["instance_method","GraphQL::Schema::TypeMembership#initialize"] + - ["instance_method","GraphQL::Schema::TypeMembership#visible?"] + - ["instance_method","GraphQL::Schema::Validator#initialize"] + - ["instance_method","GraphQL::Schema::Validator#partial_format"] + - ["instance_method","GraphQL::Schema::Validator#permitted_empty_value?"] + - ["instance_method","GraphQL::Schema::Validator#validate"] + - ["instance_method","GraphQL::Schema::Validator#validation_parameter"] + - ["instance_method","GraphQL::Schema::Validator::ExclusionValidator#initialize"] + - ["instance_method","GraphQL::Schema::Validator::FormatValidator#initialize"] + - ["instance_method","GraphQL::Schema::Validator::InclusionValidator#initialize"] + - ["instance_method","GraphQL::Schema::Validator::LengthValidator#initialize"] + - ["instance_method","GraphQL::Schema::Validator::NumericalityValidator#initialize"] + - ["instance_method","GraphQL::Schema::Validator::RequiredValidator#initialize"] + - ["instance_method","GraphQL::Schema::Visibility#dup_for"] + - ["instance_method","GraphQL::Schema::Visibility#introspection_system_configured"] + - ["instance_method","GraphQL::Schema::Visibility#mutation_configured"] + - ["instance_method","GraphQL::Schema::Visibility#orphan_types_configured"] + - ["instance_method","GraphQL::Schema::Visibility#query_configured"] + - ["instance_method","GraphQL::Schema::Visibility#subscription_configured"] + - ["instance_method","GraphQL::Schema::Warden#arguments"] + - ["instance_method","GraphQL::Schema::Warden#enum_values"] + - ["instance_method","GraphQL::Schema::Warden#fields"] + - ["instance_method","GraphQL::Schema::Warden#get_argument"] + - ["instance_method","GraphQL::Schema::Warden#get_field"] + - ["instance_method","GraphQL::Schema::Warden#get_type"] + - ["instance_method","GraphQL::Schema::Warden#initialize"] + - ["instance_method","GraphQL::Schema::Warden#interfaces"] + - ["instance_method","GraphQL::Schema::Warden#loadable?"] + - ["instance_method","GraphQL::Schema::Warden#loadable_possible_types"] + - ["instance_method","GraphQL::Schema::Warden#possible_types"] + - ["instance_method","GraphQL::Schema::Warden#reachable_type?"] + - ["instance_method","GraphQL::Schema::Warden#reachable_types"] + - ["instance_method","GraphQL::Schema::Warden#types"] + - ["instance_method","GraphQL::Schema::Warden#visible_field?"] + - ["instance_method","GraphQL::Schema::Warden::NullWarden#enum_values"] + - ["instance_method","GraphQL::Schema::Warden::NullWarden#fields"] + - ["instance_method","GraphQL::Schema::Warden::NullWarden#get_argument"] + - ["instance_method","GraphQL::Schema::Warden::NullWarden#get_type"] + - ["instance_method","GraphQL::Schema::Warden::NullWarden#reachable_types"] + - ["instance_method","GraphQL::Schema::Warden::NullWarden#types"] + - ["instance_method","GraphQL::Schema::Warden::VisibilityProfile#loadable?"] + - ["instance_method","GraphQL::StaticValidation::ArgumentLiteralsAreCompatibleError#to_h"] + - ["instance_method","GraphQL::StaticValidation::ArgumentNamesAreUniqueError#to_h"] + - ["instance_method","GraphQL::StaticValidation::ArgumentsAreDefinedError#to_h"] + - ["instance_method","GraphQL::StaticValidation::BaseVisitor#path"] + - ["instance_method","GraphQL::StaticValidation::BaseVisitor::ContextMethods#argument_definition"] + - ["instance_method","GraphQL::StaticValidation::BaseVisitor::ContextMethods#directive_definition"] + - ["instance_method","GraphQL::StaticValidation::BaseVisitor::ContextMethods#field_definition"] + - ["instance_method","GraphQL::StaticValidation::BaseVisitor::ContextMethods#parent_type_definition"] + - ["instance_method","GraphQL::StaticValidation::BaseVisitor::ContextMethods#type_definition"] + - ["instance_method","GraphQL::StaticValidation::DefinitionDependencies#dependency_map"] + - ["instance_method","GraphQL::StaticValidation::DefinitionDependencies::DependencyMap#[]"] + - ["instance_method","GraphQL::StaticValidation::DirectivesAreDefinedError#to_h"] + - ["instance_method","GraphQL::StaticValidation::DirectivesAreInValidLocationsError#to_h"] + - ["instance_method","GraphQL::StaticValidation::Error#to_h"] + - ["instance_method","GraphQL::StaticValidation::Error::ErrorHelper#error"] + - ["instance_method","GraphQL::StaticValidation::FieldsAreDefinedOnTypeError#to_h"] + - ["instance_method","GraphQL::StaticValidation::FieldsHaveAppropriateSelectionsError#to_h"] + - ["instance_method","GraphQL::StaticValidation::FieldsWillMergeError#to_h"] + - ["instance_method","GraphQL::StaticValidation::FragmentNamesAreUniqueError#to_h"] + - ["instance_method","GraphQL::StaticValidation::FragmentSpreadsArePossibleError#to_h"] + - ["instance_method","GraphQL::StaticValidation::FragmentTypesExistError#to_h"] + - ["instance_method","GraphQL::StaticValidation::FragmentsAreFiniteError#to_h"] + - ["instance_method","GraphQL::StaticValidation::FragmentsAreNamedError#to_h"] + - ["instance_method","GraphQL::StaticValidation::FragmentsAreOnCompositeTypesError#to_h"] + - ["instance_method","GraphQL::StaticValidation::FragmentsAreUsedError#to_h"] + - ["instance_method","GraphQL::StaticValidation::InputObjectNamesAreUniqueError#to_h"] + - ["instance_method","GraphQL::StaticValidation::MutationRootExistsError#to_h"] + - ["instance_method","GraphQL::StaticValidation::NoDefinitionsArePresentError#to_h"] + - ["instance_method","GraphQL::StaticValidation::NotSingleSubscriptionError#to_h"] + - ["instance_method","GraphQL::StaticValidation::OneOfInputObjectsAreValidError#to_h"] + - ["instance_method","GraphQL::StaticValidation::OperationNamesAreValidError#to_h"] + - ["instance_method","GraphQL::StaticValidation::QueryRootExistsError#to_h"] + - ["instance_method","GraphQL::StaticValidation::RequiredArgumentsArePresentError#to_h"] + - ["instance_method","GraphQL::StaticValidation::RequiredInputObjectAttributesArePresentError#to_h"] + - ["instance_method","GraphQL::StaticValidation::SubscriptionRootExistsError#to_h"] + - ["instance_method","GraphQL::StaticValidation::UniqueDirectivesPerLocationError#to_h"] + - ["instance_method","GraphQL::StaticValidation::ValidationTimeoutError#to_h"] + - ["instance_method","GraphQL::StaticValidation::Validator#handle_timeout"] + - ["instance_method","GraphQL::StaticValidation::Validator#initialize"] + - ["instance_method","GraphQL::StaticValidation::Validator#validate"] + - ["instance_method","GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError#to_h"] + - ["instance_method","GraphQL::StaticValidation::VariableNamesAreUniqueError#to_h"] + - ["instance_method","GraphQL::StaticValidation::VariableUsagesAreAllowedError#to_h"] + - ["instance_method","GraphQL::StaticValidation::VariablesAreInputTypesError#to_h"] + - ["instance_method","GraphQL::StaticValidation::VariablesAreUsedAndDefined#on_fragment_spread"] + - ["instance_method","GraphQL::StaticValidation::VariablesAreUsedAndDefined#on_variable_identifier"] + - ["instance_method","GraphQL::StaticValidation::VariablesAreUsedAndDefinedError#to_h"] + - ["instance_method","GraphQL::Subscriptions#broadcastable?"] + - ["instance_method","GraphQL::Subscriptions#build_id"] + - ["instance_method","GraphQL::Subscriptions#delete_subscription"] + - ["instance_method","GraphQL::Subscriptions#deliver"] + - ["instance_method","GraphQL::Subscriptions#execute"] + - ["instance_method","GraphQL::Subscriptions#execute_all"] + - ["instance_method","GraphQL::Subscriptions#execute_update"] + - ["instance_method","GraphQL::Subscriptions#finish_subscriptions"] + - ["instance_method","GraphQL::Subscriptions#initialize"] + - ["instance_method","GraphQL::Subscriptions#initialize_subscriptions"] + - ["instance_method","GraphQL::Subscriptions#normalize_name"] + - ["instance_method","GraphQL::Subscriptions#read_subscription"] + - ["instance_method","GraphQL::Subscriptions#trigger"] + - ["instance_method","GraphQL::Subscriptions#validate_update?"] + - ["instance_method","GraphQL::Subscriptions#write_subscription"] + - ["instance_method","GraphQL::Subscriptions::ActionCableSubscriptions#delete_subscription"] + - ["instance_method","GraphQL::Subscriptions::ActionCableSubscriptions#deliver"] + - ["instance_method","GraphQL::Subscriptions::ActionCableSubscriptions#execute_all"] + - ["instance_method","GraphQL::Subscriptions::ActionCableSubscriptions#initialize"] + - ["instance_method","GraphQL::Subscriptions::ActionCableSubscriptions#load_action_cable_message"] + - ["instance_method","GraphQL::Subscriptions::ActionCableSubscriptions#read_subscription"] + - ["instance_method","GraphQL::Subscriptions::ActionCableSubscriptions#setup_stream"] + - ["instance_method","GraphQL::Subscriptions::ActionCableSubscriptions#write_subscription"] + - ["instance_method","GraphQL::Subscriptions::BroadcastAnalyzer#analyze?"] + - ["instance_method","GraphQL::Subscriptions::BroadcastAnalyzer#result"] + - ["instance_method","GraphQL::Subscriptions::Event#fingerprint"] + - ["instance_method","GraphQL::Testing::MockActionCable::MockChannel#stream_from"] + - ["instance_method","GraphQL::Tracing::AppOpticsTrace#execute_field"] + - ["instance_method","GraphQL::Tracing::AppOpticsTrace#execute_field_lazy"] + - ["instance_method","GraphQL::Tracing::AppsignalTrace#initialize"] + - ["instance_method","GraphQL::Tracing::AppsignalTracing#initialize"] + - ["instance_method","GraphQL::Tracing::DataDogTracing#prepare_span"] + - ["instance_method","GraphQL::Tracing::DetailedTrace#debug?"] + - ["instance_method","GraphQL::Tracing::DetailedTrace#delete_all_traces"] + - ["instance_method","GraphQL::Tracing::DetailedTrace#delete_trace"] + - ["instance_method","GraphQL::Tracing::DetailedTrace#find_trace"] + - ["instance_method","GraphQL::Tracing::DetailedTrace#save_trace"] + - ["instance_method","GraphQL::Tracing::DetailedTrace#traces"] + - ["instance_method","GraphQL::Tracing::MonitorTrace::Monitor#transaction_name"] + - ["instance_method","GraphQL::Tracing::NewRelicTracing#initialize"] + - ["instance_method","GraphQL::Tracing::NotificationsTrace#initialize"] + - ["instance_method","GraphQL::Tracing::NotificationsTracing#initialize"] + - ["instance_method","GraphQL::Tracing::NotificationsTracing#trace"] + - ["instance_method","GraphQL::Tracing::PerfettoTrace#initialize"] + - ["instance_method","GraphQL::Tracing::PerfettoTrace#write"] + - ["instance_method","GraphQL::Tracing::ScoutTracing#initialize"] + - ["instance_method","GraphQL::Tracing::StatsdTracing#initialize"] + - ["instance_method","GraphQL::Tracing::Trace#analyze_multiplex"] + - ["instance_method","GraphQL::Tracing::Trace#begin_analyze_multiplex"] + - ["instance_method","GraphQL::Tracing::Trace#begin_authorized"] + - ["instance_method","GraphQL::Tracing::Trace#begin_dataloader"] + - ["instance_method","GraphQL::Tracing::Trace#begin_dataloader_source"] + - ["instance_method","GraphQL::Tracing::Trace#begin_execute_field"] + - ["instance_method","GraphQL::Tracing::Trace#begin_resolve_type"] + - ["instance_method","GraphQL::Tracing::Trace#dataloader_fiber_exit"] + - ["instance_method","GraphQL::Tracing::Trace#dataloader_fiber_resume"] + - ["instance_method","GraphQL::Tracing::Trace#dataloader_fiber_yield"] + - ["instance_method","GraphQL::Tracing::Trace#dataloader_spawn_execution_fiber"] + - ["instance_method","GraphQL::Tracing::Trace#dataloader_spawn_source_fiber"] + - ["instance_method","GraphQL::Tracing::Trace#end_analyze_multiplex"] + - ["instance_method","GraphQL::Tracing::Trace#end_authorized"] + - ["instance_method","GraphQL::Tracing::Trace#end_dataloader"] + - ["instance_method","GraphQL::Tracing::Trace#end_dataloader_source"] + - ["instance_method","GraphQL::Tracing::Trace#end_execute_field"] + - ["instance_method","GraphQL::Tracing::Trace#end_resolve_type"] + - ["instance_method","GraphQL::Tracing::Trace#execute_multiplex"] + - ["instance_method","GraphQL::Tracing::Trace#initialize"] + - ["instance_method","GraphQL::Tracing::Trace#lex"] + - ["instance_method","GraphQL::Tracing::Trace#parse"] + - ["instance_method","GraphQL::Tracing::Traceable#trace"] + - ["instance_method","GraphQL::TypeKinds::TypeKind#abstract?"] + - ["instance_method","GraphQL::TypeKinds::TypeKind#composite?"] + - ["instance_method","GraphQL::TypeKinds::TypeKind#fields?"] + - ["instance_method","GraphQL::TypeKinds::TypeKind#input?"] + - ["instance_method","GraphQL::TypeKinds::TypeKind#leaf?"] + - ["instance_method","GraphQL::TypeKinds::TypeKind#resolves?"] + - ["instance_method","GraphQL::TypeKinds::TypeKind#wraps?"] + - ["instance_method","GraphQL::Types::Relay::ConnectionBehaviors::ClassMethods#edge_nullable"] + - ["instance_method","GraphQL::Types::Relay::ConnectionBehaviors::ClassMethods#edges_nullable"] + - ["instance_method","GraphQL::Types::Relay::ConnectionBehaviors::ClassMethods#has_nodes_field"] + - ["instance_method","GraphQL::Types::Relay::ConnectionBehaviors::ClassMethods#node_nullable"] + - ["instance_method","GraphQL::Types::Relay::ConnectionBehaviors::ClassMethods#nodes_field"] + - ["instance_method","GraphQL::Types::Relay::ConnectionBehaviors::ClassMethods#reauthorize_scoped_objects"] + - ["instance_method","GraphQL::Types::Relay::ConnectionBehaviors::ClassMethods#scope_items"] + - ["module","GraphQL::Autoload"] + - ["module","GraphQL::Current"] + - ["module","GraphQL::Execution::DirectiveChecks"] + - ["module","GraphQL::Language::Generation"] + - ["module","GraphQL::Query::Fingerprint"] + - ["module","GraphQL::Query::Runnable"] + - ["module","GraphQL::Schema::Base64Encoder"] + - ["module","GraphQL::Schema::DefaultTraceClass"] + - ["module","GraphQL::Schema::Loader"] + - ["module","GraphQL::Schema::Member::BaseDSLMethods"] + - ["module","GraphQL::Schema::Member::BaseDSLMethods::ConfigurationExtension"] + - ["module","GraphQL::Schema::Member::BuildType"] + - ["module","GraphQL::Schema::Member::GraphQLTypeNames"] + - ["module","GraphQL::Schema::Member::HasDataloader"] + - ["module","GraphQL::Schema::Member::HasFields"] + - ["module","GraphQL::Schema::Member::HasUnresolvedTypeError"] + - ["module","GraphQL::Schema::ResolveTypeWithType"] + - ["module","GraphQL::Schema::Resolver::HasPayloadType"] + - ["module","GraphQL::Schema::TypeExpression"] + - ["module","GraphQL::StaticValidation::DefinitionDependencies"] + - ["module","GraphQL::StaticValidation::Error::ErrorHelper"] + - ["module","GraphQL::StaticValidation::FieldsHaveAppropriateSelections"] + - ["module","GraphQL::StaticValidation::VariablesAreUsedAndDefined"] + - ["module","GraphQL::Subscriptions::Serialize"] + - ["module","GraphQL::Tracing::ActiveSupportNotificationsTrace"] + - ["module","GraphQL::Tracing::ActiveSupportNotificationsTracing"] + - ["module","GraphQL::Tracing::AppOpticsTrace"] + - ["module","GraphQL::Tracing::CallLegacyTracers"] + - ["module","GraphQL::Tracing::MonitorTrace"] + - ["module","GraphQL::Tracing::NotificationsTrace"] + - ["module","GraphQL::Tracing::PerfettoTrace"] + - ["module","GraphQL::Tracing::Traceable"] + - ["module","GraphQL::TypeKinds"] + - ["module","GraphQL::Types::Relay"] + - ["module","GraphQL::Types::Relay::HasNodeField"] + - ["module","GraphQL::Types::Relay::HasNodesField"] + - ["module","GraphQL::Types::Relay::Node"] +allowed_missing: + - ["class","GraphQL::Schema::Argument"] + - ["class","GraphQL::Schema::Field"] + - ["class_method","GraphQL::Analysis.analyze_multiplex"] + - ["class_method","GraphQL::Analysis.analyze_query"] + - ["class_method","GraphQL::Execution::DirectiveChecks.include?"] + - ["class_method","GraphQL::Language::Nodes::Field.from_a"] + - ["class_method","GraphQL::Schema::Member::BuildType.constantize"] + - ["class_method","GraphQL::Schema::Member::BuildType.parse_type"] + - ["class_method","GraphQL::Schema::UniqueWithinType.decode"] + - ["class_method","GraphQL::Schema::UniqueWithinType.encode"] + - ["class_method","GraphQL::Subscriptions::Serialize.dump"] + - ["class_method","GraphQL::Subscriptions::Serialize.dump_recursive"] + - ["class_method","GraphQL::Subscriptions::Serialize.load"] + - ["class_method","GraphQL::Tracing::LegacyHooksTrace::RunHooks.call_hooks"] + - ["class_method","GraphQL::Tracing::LegacyHooksTrace::RunHooks.each_query_call_hooks"] + - ["instance_method","GraphQL::Analysis::QueryComplexity#initialize"] + - ["instance_method","GraphQL::Analysis::QueryComplexity::ScopedTypeComplexity#initialize"] + - ["instance_method","GraphQL::Execution::Interpreter::Arguments#initialize"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#continue_value"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#evaluate_selection_with_args"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#evaluate_selection_with_resolved_keyword_args"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#resolve_list_item"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime::GraphQLResult#initialize"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime::GraphQLResultArray#initialize"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime::GraphQLResultHash#initialize"] + - ["instance_method","GraphQL::Execution::Lazy#initialize"] + - ["instance_method","GraphQL::Execution::Lookahead#initialize"] + - ["instance_method","GraphQL::Execution::Lookahead::NullLookahead#initialize"] + - ["instance_method","GraphQL::Language::Nodes::FieldDefinition#fields"] + - ["instance_method","GraphQL::Pagination::Connection#initialize"] + - ["instance_method","GraphQL::Query#initialize"] + - ["instance_method","GraphQL::Query::Context#initialize"] + - ["instance_method","GraphQL::Query::Partial#initialize"] + - ["instance_method","GraphQL::RakeTask#initialize"] + - ["instance_method","GraphQL::Relay::RangeAdd#initialize"] + - ["instance_method","GraphQL::Schema::Argument#graphql_name"] + - ["instance_method","GraphQL::Schema::Argument#initialize"] + - ["instance_method","GraphQL::Schema::Field#graphql_name"] + - ["instance_method","GraphQL::Schema::Field#initialize"] + - ["instance_method","GraphQL::Schema::Field#mutation"] + - ["instance_method","GraphQL::Schema::FieldExtension#initialize"] + - ["instance_method","GraphQL::Schema::LateBoundType#graphql_name"] + - ["instance_method","GraphQL::Schema::Printer#initialize"] + - ["instance_method","GraphQL::Schema::Resolver#initialize"] + - ["instance_method","GraphQL::Schema::Resolver::HasPayloadType#type_expr"] + - ["instance_method","GraphQL::Schema::Subscription#initialize"] + - ["instance_method","GraphQL::Schema::Timeout::Trace#initialize"] + - ["instance_method","GraphQL::Schema::TypeMembership#initialize"] + - ["instance_method","GraphQL::Schema::Validator#initialize"] + - ["instance_method","GraphQL::Schema::Validator::ExclusionValidator#initialize"] + - ["instance_method","GraphQL::Schema::Validator::FormatValidator#initialize"] + - ["instance_method","GraphQL::Schema::Validator::InclusionValidator#initialize"] + - ["instance_method","GraphQL::Schema::Validator::LengthValidator#initialize"] + - ["instance_method","GraphQL::Schema::Validator::NumericalityValidator#initialize"] + - ["instance_method","GraphQL::Schema::Validator::RequiredValidator#initialize"] + - ["instance_method","GraphQL::Schema::Warden#initialize"] + - ["instance_method","GraphQL::Schema::Warden::NullWarden#enum_values"] + - ["instance_method","GraphQL::Schema::Warden::NullWarden#fields"] + - ["instance_method","GraphQL::Schema::Warden::NullWarden#get_argument"] + - ["instance_method","GraphQL::Schema::Warden::NullWarden#get_type"] + - ["instance_method","GraphQL::Schema::Warden::NullWarden#reachable_types"] + - ["instance_method","GraphQL::Schema::Warden::NullWarden#types"] + - ["instance_method","GraphQL::Schema::Warden::VisibilityProfile#loadable?"] + - ["instance_method","GraphQL::StaticValidation::Validator#initialize"] + - ["instance_method","GraphQL::Subscriptions#initialize"] + - ["instance_method","GraphQL::Subscriptions::ActionCableSubscriptions#initialize"] + - ["instance_method","GraphQL::Tracing::AppOpticsTrace#execute_field_lazy"] + - ["instance_method","GraphQL::Tracing::AppsignalTrace#initialize"] + - ["instance_method","GraphQL::Tracing::AppsignalTracing#initialize"] + - ["instance_method","GraphQL::Tracing::NewRelicTracing#initialize"] + - ["instance_method","GraphQL::Tracing::NotificationsTrace#initialize"] + - ["instance_method","GraphQL::Tracing::NotificationsTracing#initialize"] + - ["instance_method","GraphQL::Tracing::PerfettoTrace#initialize"] + - ["instance_method","GraphQL::Tracing::ScoutTracing#initialize"] + - ["instance_method","GraphQL::Tracing::StatsdTracing#initialize"] + - ["instance_method","GraphQL::Tracing::Trace#initialize"] + - ["class","GraphQL::Execution::Interpreter::Runtime"] + - ["class","GraphQL::Execution::Lazy"] + - ["class","GraphQL::Execution::Lazy::LazyMethodMap"] + - ["class","GraphQL::Execution::Multiplex"] + - ["class","GraphQL::Execution::Skip"] + - ["class","GraphQL::Language::DocumentFromSchemaDefinition"] + - ["class","GraphQL::Query::ValidationPipeline"] + - ["class","GraphQL::Schema::BuildFromDefinition::ResolveMap"] + - ["class","GraphQL::Schema::LateBoundType"] + - ["class","GraphQL::Schema::Member"] + - ["class","GraphQL::Schema::Warden"] + - ["class","GraphQL::Subscriptions::BroadcastAnalyzer"] + - ["class","GraphQL::Testing::MockActionCable::MockStream"] + - ["class","GraphQL::Tracing::NotificationsTrace::ActiveSupportNotificationsAdapter"] + - ["class","GraphQL::Tracing::NotificationsTrace::Adapter"] + - ["class","GraphQL::Tracing::NotificationsTrace::DryMonitorAdapter"] + - ["class","GraphQL::Tracing::PlatformTracing"] + - ["class_method","GraphQL::Execution::Interpreter.run_all"] + - ["class_method","GraphQL::Execution::Lazy.all"] + - ["class_method","GraphQL::Language.serialize"] + - ["class_method","GraphQL::Query::Fingerprint.generate"] + - ["class_method","GraphQL::Schema.add_subscription_extension_if_necessary"] + - ["class_method","GraphQL::Schema.after_any_lazies"] + - ["class_method","GraphQL::Schema.after_lazy"] + - ["class_method","GraphQL::Schema.handle_or_reraise"] + - ["class_method","GraphQL::Schema.root_type_for_operation"] + - ["class_method","GraphQL::Schema.sync_lazy"] + - ["class_method","GraphQL::Schema.use_visibility_profile?"] + - ["class_method","GraphQL::Schema::InputObject.coerce_result"] + - ["class_method","GraphQL::Schema::InputObject.has_no_arguments"] + - ["class_method","GraphQL::Schema::InputObject.has_no_arguments?"] + - ["class_method","GraphQL::Schema::Resolver.extensions"] + - ["class_method","GraphQL::Schema::TypeExpression.build_type"] + - ["class_method","GraphQL::Schema::Union.assign_type_membership_object_type"] + - ["class_method","GraphQL::Schema::Warden.visible_entry?"] + - ["instance_method","GraphQL::Dataloader#append_job"] + - ["instance_method","GraphQL::Dataloader#lazy_at_depth"] + - ["instance_method","GraphQL::Dataloader#queue_pending_source"] + - ["instance_method","GraphQL::Dataloader::Source#run_pending_keys"] + - ["instance_method","GraphQL::Dataloader::Source#setup"] + - ["instance_method","GraphQL::Execution::Interpreter::Arguments#merge_extras"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#after_lazy"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#continue_field"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#directives_include?"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#evaluate_selection"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#evaluate_selections"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#run_eager"] + - ["instance_method","GraphQL::Execution::Interpreter::Runtime#set_graphql_dead"] + - ["instance_method","GraphQL::Execution::Lazy#then"] + - ["instance_method","GraphQL::Execution::Lazy#value"] + - ["instance_method","GraphQL::Execution::Lazy::LazyMethodMap#get"] + - ["instance_method","GraphQL::Execution::Lazy::LazyMethodMap#set"] + - ["instance_method","GraphQL::Pagination::Connections#edge_class_for_field"] + - ["instance_method","GraphQL::Pagination::Connections#wrap"] + - ["instance_method","GraphQL::Query::Runnable#handle_or_reraise"] + - ["instance_method","GraphQL::Query::ValidationPipeline#valid?"] + - ["instance_method","GraphQL::Query::ValidationPipeline#validation_errors"] + - ["instance_method","GraphQL::Schema::Argument#coerce_into_values"] + - ["instance_method","GraphQL::Schema::Argument#prepare_value"] + - ["instance_method","GraphQL::Schema::Argument#validate_default_value"] + - ["instance_method","GraphQL::Schema::BuildFromDefinition::Builder#replace_late_bound_types_with_built_in"] + - ["instance_method","GraphQL::Schema::Field#ensure_loaded"] + - ["instance_method","GraphQL::Schema::FieldExtension#after_define_apply"] + - ["instance_method","GraphQL::Schema::InputObject#validate_for"] + - ["instance_method","GraphQL::Schema::IntrospectionSystem#resolve_late_bindings"] + - ["instance_method","GraphQL::Schema::Member::BaseDSLMethods#comment"] + - ["instance_method","GraphQL::Schema::Member::BaseDSLMethods#description"] + - ["instance_method","GraphQL::Schema::Member::BaseDSLMethods#introspection"] + - ["instance_method","GraphQL::Schema::Member::BaseDSLMethods#mutation"] + - ["instance_method","GraphQL::Schema::Member::HasArguments#coerce_arguments"] + - ["instance_method","GraphQL::Schema::Member::HasFields#add_field"] + - ["instance_method","GraphQL::Schema::Member::HasFields#field_class"] + - ["instance_method","GraphQL::Schema::Member::HasFields#has_no_fields"] + - ["instance_method","GraphQL::Schema::Member::HasFields#has_no_fields?"] + - ["instance_method","GraphQL::Schema::Member::HasFields#own_fields"] + - ["instance_method","GraphQL::Schema::Mutation#call_resolve"] + - ["instance_method","GraphQL::Schema::Resolver#call_resolve"] + - ["instance_method","GraphQL::Schema::Resolver#resolve_with_support"] + - ["instance_method","GraphQL::Schema::Subscription#call_resolve"] + - ["instance_method","GraphQL::Schema::Subscription#resolve_subscribe"] + - ["instance_method","GraphQL::Schema::Subscription#resolve_update"] + - ["instance_method","GraphQL::Schema::Subscription#resolve_with_support"] + - ["instance_method","GraphQL::Schema::Visibility#dup_for"] + - ["instance_method","GraphQL::Schema::Visibility#introspection_system_configured"] + - ["instance_method","GraphQL::Schema::Visibility#mutation_configured"] + - ["instance_method","GraphQL::Schema::Visibility#orphan_types_configured"] + - ["instance_method","GraphQL::Schema::Visibility#query_configured"] + - ["instance_method","GraphQL::Schema::Visibility#subscription_configured"] + - ["instance_method","GraphQL::Schema::Warden#arguments"] + - ["instance_method","GraphQL::Schema::Warden#enum_values"] + - ["instance_method","GraphQL::Schema::Warden#fields"] + - ["instance_method","GraphQL::Schema::Warden#get_argument"] + - ["instance_method","GraphQL::Schema::Warden#get_field"] + - ["instance_method","GraphQL::Schema::Warden#get_type"] + - ["instance_method","GraphQL::Schema::Warden#interfaces"] + - ["instance_method","GraphQL::Schema::Warden#loadable?"] + - ["instance_method","GraphQL::Schema::Warden#loadable_possible_types"] + - ["instance_method","GraphQL::Schema::Warden#possible_types"] + - ["instance_method","GraphQL::Schema::Warden#reachable_type?"] + - ["instance_method","GraphQL::Schema::Warden#reachable_types"] + - ["instance_method","GraphQL::Schema::Warden#types"] + - ["instance_method","GraphQL::Schema::Warden#visible_field?"] + - ["instance_method","GraphQL::Subscriptions::BroadcastAnalyzer#analyze?"] + - ["instance_method","GraphQL::Subscriptions::BroadcastAnalyzer#result"] + - ["instance_method","GraphQL::Tracing::Traceable#trace"] + - ["module","GraphQL::Execution::DirectiveChecks"] + - ["module","GraphQL::Query::Fingerprint"] + - ["module","GraphQL::Schema::Base64Encoder"] + - ["module","GraphQL::Schema::DefaultTraceClass"] + - ["module","GraphQL::Schema::Member::BaseDSLMethods"] + - ["module","GraphQL::Schema::Member::BuildType"] + - ["module","GraphQL::Schema::Member::GraphQLTypeNames"] + - ["module","GraphQL::Schema::TypeExpression"] + - ["module","GraphQL::Subscriptions::Serialize"] + - ["module","GraphQL::Tracing::Traceable"] +allowed_extra: + - ["class","GraphQL::Dashboard"] + - ["class_method","GraphQL.default_parser"] + - ["class_method","GraphQL::Analysis::QueryComplexity.new"] + - ["class_method","GraphQL::Analysis::QueryComplexity::ScopedTypeComplexity.new"] + - ["class_method","GraphQL::Execution::Interpreter::Arguments.new"] + - ["class_method","GraphQL::Execution::Lazy.new"] + - ["class_method","GraphQL::Execution::Lookahead.new"] + - ["class_method","GraphQL::Execution::Lookahead::NullLookahead.new"] + - ["class_method","GraphQL::Pagination::Connection.new"] + - ["class_method","GraphQL::Query.new"] + - ["class_method","GraphQL::Query::Context.new"] + - ["class_method","GraphQL::Query::Partial.new"] + - ["class_method","GraphQL::RakeTask.new"] + - ["class_method","GraphQL::Relay::RangeAdd.new"] + - ["class_method","GraphQL::Schema.connections"] + - ["class_method","GraphQL::Schema.dataloader_class"] + - ["class_method","GraphQL::Schema.visibility_profile_class"] + - ["class_method","GraphQL::Schema.warden_class"] + - ["class_method","GraphQL::Schema::Argument.new"] + - ["class_method","GraphQL::Schema::Field.new"] + - ["class_method","GraphQL::Schema::FieldExtension.new"] + - ["class_method","GraphQL::Schema::Object.wrap_scoped"] + - ["class_method","GraphQL::Schema::Printer.new"] + - ["class_method","GraphQL::Schema::Resolver.new"] + - ["class_method","GraphQL::Schema::Subscription.new"] + - ["class_method","GraphQL::Schema::Timeout::Trace.new"] + - ["class_method","GraphQL::Schema::TypeMembership.new"] + - ["class_method","GraphQL::Schema::Validator.new"] + - ["class_method","GraphQL::Schema::Validator::ExclusionValidator.new"] + - ["class_method","GraphQL::Schema::Validator::FormatValidator.new"] + - ["class_method","GraphQL::Schema::Validator::InclusionValidator.new"] + - ["class_method","GraphQL::Schema::Validator::LengthValidator.new"] + - ["class_method","GraphQL::Schema::Validator::NumericalityValidator.new"] + - ["class_method","GraphQL::Schema::Validator::RequiredValidator.new"] + - ["class_method","GraphQL::Schema::Warden.new"] + - ["class_method","GraphQL::StaticValidation::Validator.new"] + - ["class_method","GraphQL::Subscriptions.new"] + - ["class_method","GraphQL::Subscriptions::ActionCableSubscriptions.new"] + - ["class_method","GraphQL::Tracing::AppOpticsTrace.version"] + - ["class_method","GraphQL::Tracing::AppsignalTrace.new"] + - ["class_method","GraphQL::Tracing::AppsignalTracing.new"] + - ["class_method","GraphQL::Tracing::NewRelicTracing.new"] + - ["class_method","GraphQL::Tracing::NotificationsTrace.new"] + - ["class_method","GraphQL::Tracing::NotificationsTracing.new"] + - ["class_method","GraphQL::Tracing::PerfettoTrace.new"] + - ["class_method","GraphQL::Tracing::ScoutTracing.new"] + - ["class_method","GraphQL::Tracing::StatsdTracing.new"] + - ["class_method","GraphQL::Tracing::Trace.new"] + - ["constant","GraphQL::Analysis::QueryComplexity::ScopedTypeComplexity::DEFAULT_PROC"] + - ["constant","GraphQL::Backtrace::TracedError::CAUSE_BACKTRACE_PREVIEW_LENGTH"] + - ["constant","GraphQL::Dashboard"] + - ["constant","GraphQL::Execution::Interpreter::NO_OPERATION"] + - ["constant","GraphQL::Execution::Lazy::NullResult"] + - ["constant","GraphQL::Execution::Lookahead::NULL_LOOKAHEAD"] + - ["constant","GraphQL::Introspection::INTROSPECTION_QUERY"] + - ["constant","GraphQL::Language::EFFICIENT_NUMBER_REGEXP"] + - ["constant","GraphQL::Language::Lexer::FIRST_BYTES"] + - ["constant","GraphQL::Language::Lexer::NUMERIC_REGEXP"] + - ["constant","GraphQL::Language::Lexer::PUNCTUATION_NAME_FOR_BYTE"] + - ["constant","GraphQL::Language::Visitor::DELETE_NODE"] + - ["constant","GraphQL::Schema::InputObject::INVALID_OBJECT_MESSAGE"] + - ["constant","GraphQL::Schema::Member::HasFields::CONFLICT_FIELD_NAMES"] + - ["constant","GraphQL::Schema::Member::HasFields::GRAPHQL_RUBY_KEYWORDS"] + - ["constant","GraphQL::Schema::Member::HasFields::RUBY_KEYWORDS"] + - ["constant","GraphQL::StaticValidation::ALL_RULES"] + - ["constant","GraphQL::StaticValidation::FieldsWillMerge::NO_ARGS"] + - ["constant","GraphQL::Tracing::ActiveSupportNotificationsTracing::KEYS"] + - ["constant","GraphQL::Tracing::AppOpticsTrace::EXEC_KEYS"] + - ["constant","GraphQL::Tracing::AppOpticsTrace::PREP_KEYS"] + - ["constant","GraphQL::Tracing::AppOpticsTracing::EXEC_KEYS"] + - ["constant","GraphQL::Tracing::AppOpticsTracing::PREP_KEYS"] + - ["constant","GraphQL::Tracing::AppsignalTrace"] + - ["constant","GraphQL::Tracing::DataDogTrace"] + - ["constant","GraphQL::Tracing::NewRelicTrace"] + - ["constant","GraphQL::Tracing::NotificationsTracing::KEYS"] + - ["constant","GraphQL::Tracing::PerfettoTrace::ArgumentsFilter::SENSITIVE_KEY"] + - ["constant","GraphQL::Tracing::PerfettoTrace::PROTOBUF_AVAILABLE"] + - ["constant","GraphQL::Tracing::PrometheusTrace"] + - ["constant","GraphQL::Tracing::PrometheusTracing::GraphQLCollector"] + - ["constant","GraphQL::Tracing::ScoutTrace"] + - ["constant","GraphQL::Tracing::SentryTrace"] + - ["constant","GraphQL::Tracing::StatsdTrace"] + - ["constant","GraphQL::Types::ISO8601DateTime::DEFAULT_TIME_PRECISION"] + - ["constant","Graphql::Dashboard::StaticsController::STATICS"] + - ["instance_method","GraphQL::Analysis#analyze_multiplex"] + - ["instance_method","GraphQL::Analysis#analyze_query"] + - ["instance_method","GraphQL::Execution::DirectiveChecks#include?"] + - ["instance_method","GraphQL::Pagination::Connection#first"] + - ["instance_method","GraphQL::Pagination::Connection#last"] + - ["instance_method","GraphQL::Query#query_string"] + - ["instance_method","GraphQL::Query#result_values="] + - ["instance_method","GraphQL::Query#static_validator="] + - ["instance_method","GraphQL::Query#validate="] + - ["instance_method","GraphQL::Query::Context#warden"] + - ["instance_method","GraphQL::Schema::Argument#comment"] + - ["instance_method","GraphQL::Schema::Argument#description"] + - ["instance_method","GraphQL::Schema::Field#:best_score"] + - ["instance_method","GraphQL::Schema::Field#:itself"] + - ["instance_method","GraphQL::Schema::Field#description"] + - ["instance_method","GraphQL::Schema::Field#subscription_scope"] + - ["instance_method","GraphQL::Schema::Field#type"] + - ["instance_method","GraphQL::Schema::Member::BaseDSLMethods#default_graphql_name"] + - ["instance_method","GraphQL::Schema::Member::BaseDSLMethods#graphql_name"] + - ["instance_method","GraphQL::Schema::Member::BuildType#constantize"] + - ["instance_method","GraphQL::Schema::Member::BuildType#parse_type"] + - ["instance_method","GraphQL::Schema::Member::HasAstNode#ast_node"] + - ["instance_method","GraphQL::Schema::Member::HasDeprecationReason#deprecation_reason="] + - ["instance_method","GraphQL::Schema::UniqueWithinType#decode"] + - ["instance_method","GraphQL::Schema::UniqueWithinType#encode"] + - ["instance_method","GraphQL::Subscriptions::Serialize#dump"] + - ["instance_method","GraphQL::Subscriptions::Serialize#dump_recursive"] + - ["instance_method","GraphQL::Subscriptions::Serialize#load"] + - ["instance_method","GraphQL::Tracing::LegacyHooksTrace::RunHooks#call_hooks"] + - ["instance_method","GraphQL::Tracing::LegacyHooksTrace::RunHooks#each_query_call_hooks"] + - ["instance_method","GraphQL::Types::Relay::ConnectionBehaviors::ClassMethods#edge_type"] + - ["instance_method","GraphQL::Types::Relay::EdgeBehaviors::ClassMethods#node_nullable"] + - ["instance_method","GraphQL::Types::Relay::EdgeBehaviors::ClassMethods#node_type"] + - ["module","GraphQL::Schema::BuildFromDefinition::Builder"] diff --git a/graphql.gemspec b/graphql.gemspec index 3ea1c4ebc8a..6fa2573171e 100644 --- a/graphql.gemspec +++ b/graphql.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| "rubygems_mfa_required" => "true", } - s.files = Dir["{lib}/**/*", "MIT-LICENSE", "readme.md", ".yardopts"] + s.files = Dir["{lib}/**/*", "MIT-LICENSE", "readme.md", ".rdoc_options"] s.add_runtime_dependency "base64" s.add_runtime_dependency "fiber-storage" @@ -45,7 +45,6 @@ Gem::Specification.new do |s| s.add_development_dependency "simplecov" s.add_development_dependency "simplecov-lcov" s.add_development_dependency "undercover" - s.add_development_dependency "yard" s.add_development_dependency "m", "~> 1.5.0" s.add_development_dependency "mutex_m" s.add_development_dependency "webrick" diff --git a/guides/_config.yml b/guides/_config.yml deleted file mode 100644 index 33a879e261c..00000000000 --- a/guides/_config.yml +++ /dev/null @@ -1,42 +0,0 @@ -title: 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" - fullwidth: true - -algolia: - application_id: '8VO8708WUV' - index_name: 'prod_graphql_ruby' - settings: - searchableAttributes: - - section - - title - - headings - - content - customRanking: - - desc(title) - - desc(headings) - - desc(content) - -plugins: - - jekyll-algolia - - jekyll-redirect-from diff --git a/guides/_layouts/default.html b/guides/_layouts/default.html deleted file mode 100644 index 68adcfdc46e..00000000000 --- a/guides/_layouts/default.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - {% if page.section contains "GraphQL" %} - {{ page.section }} - {{ page.title }} - {% else %} - GraphQL - {{ page.title }} - {% endif %} - - - - - - -
-
- -
-
-
-
-
-
-
- {{ content }} -
- - - - - diff --git a/guides/_layouts/doc_stub.html b/guides/_layouts/doc_stub.html deleted file mode 100644 index c29ec196f8d..00000000000 --- a/guides/_layouts/doc_stub.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - {{ content }} - - diff --git a/guides/_layouts/guide.html b/guides/_layouts/guide.html deleted file mode 100644 index 0870ccff60e..00000000000 --- a/guides/_layouts/guide.html +++ /dev/null @@ -1,83 +0,0 @@ ---- -layout: default ---- - -{% if page.experimental %} -
-

- ⚠ Experimental ⚠ -

-

- This feature may get big changes in future releases. - Check the changelog or - subscribe to the newsletter for updates. -

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

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

-
-{% endif %} -{% if page.enterprise %} -
-

- 🌟 Enterprise Feature 🌟 - - This feature is bundled with GraphQL-Enterprise. - -

-
-{% endif %} -

{{ page.title }}

-
{% table_of_contents %}
-
- {{ content }} -
- - diff --git a/guides/_plugins/api_doc.rb b/guides/_plugins/api_doc.rb deleted file mode 100644 index 8a665479568..00000000000 --- a/guides/_plugins/api_doc.rb +++ /dev/null @@ -1,252 +0,0 @@ -# frozen_string_literal: true -require_relative "../../lib/graphql/version" -require "kramdown" - -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 CalloutBlock < Liquid::Block - def initialize(tag_name, callout_class, tokens) - super - @callout_class = callout_class.strip - end - - def render(context) - raw_text = super - - site = context.registers[:site] - converter = site.find_converter_instance(::Jekyll::Converters::Markdown) - rendered_text = converter.convert(raw_text) - - heading = case @callout_class - when "warning" - "⚠ Heads up!" - else - raise ArgumentError, "Unhandled callout class: #{@callout_class.inspect}" - end - %|

#{heading}

#{rendered_text}
| - 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 - - class TableOfContents < Liquid::Tag - def render(context) - headers = context["page"]["content"].scan(/^##+[^\n]+$/m) - section_count = 0 - current_table = header_table = [nil] - prev_depth = nil - headers.each do |h| - header_hashes = h.match(/^#+/)[0] - depth = header_hashes.size - if depth == 2 - section_count += 1 - end - text = h.gsub(/^#+ /, "") - target = text.downcase - .gsub("🟡", "00emoji00") - .gsub("❌", "00emoji00") - .gsub(/[^a-z0-9_]+/, "-") - .sub(/-$/, "") - .sub(/^-/, "") - .gsub("-00emoji00", "-") - - rendered_text = Kramdown::Document.new(text, auto_ids: false) - .to_html - .sub("

", "") - .sub("

", "") # remove wrapping added by kramdown - - if prev_depth - if prev_depth > depth - # outdent - current_table = current_table[0] - elsif prev_depth < depth - # indent - new_table = [current_table] - current_table[-1][-1] = new_table - current_table = new_table - else - # same depth - end - end - - current_table << [rendered_text, target, []] - prev_depth = depth - end - - table_html = "".dup - render_table_into_html(table_html, header_table) - - html = <<~HTML -
-

Contents

- #{table_html} -
- HTML - - if section_count == 0 - if headers.any? - full_path = "guides/#{context["page"]["path"]}" - warn("No sections identified for #{full_path} -- make sure it's using `## ...` for section headings.") - end - "" - else - html - end - end - - private - - def render_table_into_html(html_str, table) - html_str << "
    " - table.each_with_index do |entry, idx| - if idx == 0 - next # parent reference - end - rendered_text, target, child_table = *entry - html_str << "
  1. " - html_str << "#{rendered_text}" - if child_table.any? - render_table_into_html(html_str, child_table) - end - html_str << "
  2. " - end - html_str << "
" - 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) -Liquid::Template.register_tag("table_of_contents", GraphQLSite::TableOfContents) -Liquid::Template.register_tag('callout', GraphQLSite::CalloutBlock) -Jekyll::Hooks.register :site, :pre_render do |site| - section_pages = Hash.new { |h, k| h[k] = [] } - section_names = [] - site.pages.each do |page| - this_section = page.data["section"] - if this_section - this_section_pages = section_pages[this_section] - this_section_pages << page - this_section_pages.sort_by! { |page| page.data["index"] || 100 } - page.data["section_pages"] = this_section_pages - section_names << this_section - end - end - section_names.compact! - section_names.uniq! - all_sections = [] - section_names.each do |section_name| - all_sections << { - "name" => section_name, - "overview_page" => section_pages[section_name].first, - } - end - - sorted_section_names = site.pages.find { |p| p.data["title"] == "Guides Index" }.data["sections"].map { |s| s["name"] } - all_sections.sort_by! { |s| sorted_section_names.index(s["name"]) } - site.data["all_sections"] = all_sections -end - -module Jekyll - module Algolia - module Hooks - def self.before_indexing_each(record, node, context) - record = record.dup - record.delete(:section_pages) - record - end - end - end -end diff --git a/guides/_sass/reset.scss b/guides/_sass/reset.scss deleted file mode 100644 index 47c6f90962b..00000000000 --- a/guides/_sass/reset.scss +++ /dev/null @@ -1,48 +0,0 @@ -/* 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; -} diff --git a/guides/_tasks/site.rb b/guides/_tasks/site.rb deleted file mode 100644 index 3c6cfbabdf6..00000000000 --- a/guides/_tasks/site.rb +++ /dev/null @@ -1,195 +0,0 @@ -# frozen_string_literal: true -require "yard" -require "webrick" - -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") - puts "Building docs for #{version}" - # 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 - # 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}") - puts "Creating directories: #{push_dest.inspect}, #{local_dest.inspect}" - FileUtils.mkdir_p(push_dest) - FileUtils.mkdir_p(local_dest) - system("yardoc") - 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: [] do # if you need api docs, add `:build_doc` to the list of dependencies - 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 diff --git a/guides/authorization/authorization.md b/guides/authorization/authorization.md index 474b544b9d7..3f505f89351 100644 --- a/guides/authorization/authorization.md +++ b/guides/authorization/authorization.md @@ -1,11 +1,4 @@ ---- -layout: guide -search: true -section: Authorization -title: Authorization -desc: During execution, check if the current user has permission to access retrieved objects. -index: 3 ---- +# Authorization 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. @@ -62,7 +55,7 @@ class Types::BaseField < GraphQL::Schema::Field end ``` -For this to work, the base field class must be {% internal_link "configured with other GraphQL types", "/type_definitions/extensions.html#customizing-fields" %}. +For this to work, the base field class must be [configured with other GraphQL types](/type_definitions/extensions.html#customizing-fields). #### Argument Authorization @@ -85,23 +78,23 @@ class Types::BaseArgument < GraphQL::Schema::Argument end ``` -For this to work, the base argument class must be {% internal_link "configured with other GraphQL types", "/type_definitions/extensions.html#customizing-arguments" %}. +For this to work, the base argument class must be [configured with other GraphQL types](/type_definitions/extensions.html#customizing-arguments). ## Mutation Authorization -See {% internal_link "Mutation Authorization", "/mutations/mutation_authorization.html#can-this-user-perform-this-action" %} in the Mutation Guides. +See [Mutation Authorization](/mutations/mutation_authorization.html#can-this-user-perform-this-action) in the Mutation Guides. ## Enum Value Authorization -{{ "GraphQL::Schema::EnumValue#authorized?" | api_doc }} is called when client input is received and when the schema returns values to the client. +[GraphQL::Schema::EnumValue#authorized?](rdoc-ref:GraphQL::Schema::EnumValue#authorized?) is called when client input is received and when the schema returns values to the client. -For authorizing input, if a value's `#authorized?` method returns false, then a {{ "GraphQL::UnauthorizedEnumValueError" | api_doc }} is raised. It passed to your schema's `.unauthorized_object` hook, where you can handle it another way if you want. +For authorizing input, if a value's `#authorized?` method returns false, then a [GraphQL::UnauthorizedEnumValueError](rdoc-ref:GraphQL::UnauthorizedEnumValueError) is raised. It passed to your schema's `.unauthorized_object` hook, where you can handle it another way if you want. -For authorizing return values, if an outgoing value's `#authorized?` method returns false, then a {{ "GraphQL::Schema::Enum::UnresolvedValueError" | api_doc }} is raised, which crashes the query. In this case, you should modify your field or resolver to _not_ return this value to an unauthorized viewer. (In this case, the error isn't returned to the viewer because the viewer can't do anything about it -- it's a developer-facing issue instead.) +For authorizing return values, if an outgoing value's `#authorized?` method returns false, then a [GraphQL::Schema::Enum::UnresolvedValueError](rdoc-ref:GraphQL::Schema::Enum::UnresolvedValueError) is raised, which crashes the query. In this case, you should modify your field or resolver to _not_ return this value to an unauthorized viewer. (In this case, the error isn't returned to the viewer because the viewer can't do anything about it -- it's a developer-facing issue instead.) ## 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: +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](rdoc-ref:GraphQL::Schema.unauthorized_object) in your schema class, for example: ```ruby class MySchema < GraphQL::Schema diff --git a/guides/authorization/can_can_integration.md b/guides/authorization/can_can_integration.md index 09c3113eb82..214f7d53ed8 100644 --- a/guides/authorization/can_can_integration.md +++ b/guides/authorization/can_can_integration.md @@ -1,13 +1,4 @@ ---- -layout: guide -search: true -section: Authorization -title: CanCan Integration -desc: Hook up GraphQL to CanCan abilities -index: 4 -pro: true ---- - +# CanCan Integration [GraphQL::Pro](https://graphql.pro) includes an integration for powering GraphQL authorization with [CanCan](https://github.com/CanCanCommunity/cancancan). @@ -82,13 +73,13 @@ end ### 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" %}. +When any CanCan check returns `false`, the unauthorized object is passed to [Schema.unauthorized_object](rdoc-ref:GraphQL::Schema.unauthorized_object), as described in [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" %} +The CanCan integration adds [CanCan's `.accessible_by`](https://github.com/cancancommunity/cancancan/wiki/Fetching-Records) to GraphQL-Ruby's [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: @@ -193,7 +184,7 @@ class Types::BaseField end ``` -(See {{ "GraphQL::Schema::Field" | api_doc }} for the different values available for defaults.) +(See [GraphQL::Schema::Field](rdoc-ref:GraphQL::Schema::Field) for the different values available for defaults.) ### Providing a Custom CanCan Subject @@ -339,7 +330,7 @@ The method is called with: 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" %}: +Whatever that method returns will be treated as an early return value for the mutation, so for example, you could return [errors as data](/mutations/mutation_errors): ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation diff --git a/guides/authorization/overview.md b/guides/authorization/overview.md index 9e2258707ef..cc035635b20 100644 --- a/guides/authorization/overview.md +++ b/guides/authorization/overview.md @@ -1,11 +1,4 @@ ---- -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 ---- +# Overview 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. @@ -131,7 +124,7 @@ Despite the advantages of authorization at the application layer, as described a 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 "Authorization", "/authorization/authorization" %} checks application objects during execution to be sure the user has permission to access them. +- [Visibility](/authorization/visibility) hides parts of the GraphQL schema from users who don't have full permission. +- [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" %}. +Also, [GraphQL::Pro](https://graphql.pro) has integrations for [CanCan](/authorization/can_can_integration) and [Pundit](/authorization/pundit_integration). diff --git a/guides/authorization/pundit_integration.md b/guides/authorization/pundit_integration.md index 0900df6611c..19a04deac42 100644 --- a/guides/authorization/pundit_integration.md +++ b/guides/authorization/pundit_integration.md @@ -1,12 +1,4 @@ ---- -layout: guide -search: true -section: Authorization -title: Pundit Integration -desc: Hook up GraphQL to Pundit policies -index: 4 -pro: true ---- +# Pundit Integration [GraphQL::Pro](https://graphql.pro) includes an integration for powering GraphQL authorization with [Pundit](https://github.com/varvet/pundit) policies. @@ -121,11 +113,11 @@ 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" %}. +When any Policy method returns `false`, the unauthorized object is passed to [Schema.unauthorized_object](rdoc-ref:GraphQL::Schema.unauthorized_object), as described in [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. +The Pundit integration adds [Pundit scopes](https://github.com/varvet/pundit#scopes) to GraphQL-Ruby's [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: @@ -364,7 +356,7 @@ The method is called with: 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" %}: +Whatever that method returns will be treated as an early return value for the mutation, so for example, you could return [errors as data](/mutations/mutation_errors): ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation diff --git a/guides/authorization/scoping.md b/guides/authorization/scoping.md index acdfbcaaaf3..74a42186c60 100644 --- a/guides/authorization/scoping.md +++ b/guides/authorization/scoping.md @@ -1,12 +1,4 @@ ---- -layout: guide -search: true -section: Authorization -title: Scoping -desc: Filter lists to match the current viewer and context -index: 4 ---- - +# Scoping _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. diff --git a/guides/authorization/visibility.md b/guides/authorization/visibility.md index 2127b298daf..7a117c1c660 100644 --- a/guides/authorization/visibility.md +++ b/guides/authorization/visibility.md @@ -1,13 +1,4 @@ ---- -layout: guide -search: true -section: Authorization -title: Visibility -desc: Programmatically hide parts of the GraphQL schema from some users. -index: 1 -redirect_from: -- /schema/limiting_visibility ---- +# Visibility 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. @@ -110,7 +101,7 @@ class Types::BaseField < GraphQL::Schema::Field end ``` -For this to work, the base field class must be {% internal_link "configured with other GraphQL types", "/type_definitions/extensions.html#customizing-fields" %}. +For this to work, the base field class must be [configured with other GraphQL types](/type_definitions/extensions.html#customizing-fields). ## Argument Visibility @@ -128,7 +119,7 @@ class Types::BaseArgument < GraphQL::Schema::Argument end ``` -For this to work, the base argument class must be {% internal_link "configured with other GraphQL types", "/type_definitions/extensions.html#customizing-arguments" %}. +For this to work, the base argument class must be [configured with other GraphQL types](/type_definitions/extensions.html#customizing-arguments). ## Opting Out @@ -146,7 +137,7 @@ For big schemas, this can be a worthwhile speed-up. ## Migration Notes -{{ "GraphQL::Schema::Visibility" | api_doc }} is a _new_ implementation of visibility in GraphQL-Ruby. It has some slight differences from the previous implementation ({{ "GraphQL::Schema::Warden" | api_doc }}): +[GraphQL::Schema::Visibility](rdoc-ref:GraphQL::Schema::Visibility) is a _new_ implementation of visibility in GraphQL-Ruby. It has some slight differences from the previous implementation ([GraphQL::Schema::Warden](rdoc-ref:GraphQL::Schema::Warden)): - `Visibility` speeds up Rails app boot because it doesn't require all types to be loaded during boot and only loads types as they are used by queries. - `Visibility` supports predefined, reusable visibility profiles which speeds up queries using complicated `visible?` checks. diff --git a/guides/changesets/definition.md b/guides/changesets/definition.md index f46c5eda054..fdf41000b65 100644 --- a/guides/changesets/definition.md +++ b/guides/changesets/definition.md @@ -1,15 +1,6 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Changesets -title: Defining Changesets -desc: Creating a set of modifications to release in an API version -index: 2 ---- - -After {% internal_link "installing Changeset integrations", "/changesets/installation" %} in your schema, you can create Changesets which modify parts of the schema. Changesets extend `GraphQL::Enterprise::Changeset` and include a `release` string. Once a Changeset class is defined, it can be referenced with `added_in: ...` or `removed_in: ...` configurations in the schema. +# Defining Changesets + +After [installing Changeset integrations](/changesets/installation) in your schema, you can create Changesets which modify parts of the schema. Changesets extend `GraphQL::Enterprise::Changeset` and include a `release` string. Once a Changeset class is defined, it can be referenced with `added_in: ...` or `removed_in: ...` configurations in the schema. __Note:__ Before GraphQL-Enterprise 1.3.0, Changesets were configured with `modifies ...` blocks. These blocks are still supported and you can find the documentation for that API [on GitHub](https://github.com/rmosolgo/graphql-ruby/blob/v2.0.22/guides/changesets/definition.md). @@ -25,7 +16,7 @@ class Changesets::DeprecateRecipeTags < GraphQL::Enterprise::Changeset end ``` -Additionally, Changesets must be {% internal_link "released", "/changesets/releases" %} for their changes to be published. +Additionally, Changesets must be [released](/changesets/releases) for their changes to be published. ## Publishing with `added_in:` @@ -90,7 +81,7 @@ See below for the different kind of modifications you can make in a changeset: ### Fields -To add or redefine a field, use `field(..., added_in: ...)`, including all configuration values for the new implementation (see {{ "GraphQL::Schema::Field#initialize" | api_doc }}). The definition given here will override the previous definition (if there was one) whenever this Changeset applies. +To add or redefine a field, use `field(..., added_in: ...)`, including all configuration values for the new implementation (see the [GraphQL::Schema::Field](rdoc-ref:GraphQL::Schema::Field) API). The definition given here will override the previous definition (if there was one) whenever this Changeset applies. ```ruby class Types::Recipe < Types::BaseObject @@ -297,4 +288,4 @@ end Besides observability, you can use a runtime check when a resolver needs to pick a different behavior depending on the API version. -After defining a changeset, add it to the schema to {% internal_link "release it", "/changesets/releases" %}. +After defining a changeset, add it to the schema to [release it](/changesets/releases). diff --git a/guides/changesets/installation.md b/guides/changesets/installation.md index b4e004211c9..26907ca7fde 100644 --- a/guides/changesets/installation.md +++ b/guides/changesets/installation.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Changesets -title: Installing Changesets -desc: Adding Changesets to your schema -index: 1 ---- +# Installing Changesets Changesets require some updates to the schema (to define changesets) and some updates to your controller (to receive version headers from clients). @@ -60,7 +51,7 @@ To get started with [GraphQL-Enterprise](https://graphql.pro/enterprise) Changes Also, make sure that your `BaseUnion` and `BaseInterface` have `type_membership_class(Types::BaseTypeMembership)` configured in it. (`TypeMembership`s are used by GraphQL-Ruby to link object types to the union types they belong to and the interfaces they implement. By using a custom type membership class, you can make objects belong (or _not_ belong) to unions or interfaces, depending on the API version.) -Once those integrations are set up, you're ready to {% internal_link "write a changeset", "/changesets/definition" %} and start {% internal_link "releasing API versions", "/changesets/releases" %}! +Once those integrations are set up, you're ready to [write a changeset](/changesets/definition) and start [releasing API versions](/changesets/releases)! ## Controller Setup @@ -83,4 +74,4 @@ In the example above, `API-Version: ...` will be parsed from the incoming reques If `context[:changeset_version]` is `nil`, then _no_ changesets will apply to that request. -Now that Changesets are installed, read on to {% internal_link "define some changesets", "/changesets/definition" %}. +Now that Changesets are installed, read on to [define some changesets](/changesets/definition). diff --git a/guides/changesets/overview.md b/guides/changesets/overview.md index b391429f465..571db861254 100644 --- a/guides/changesets/overview.md +++ b/guides/changesets/overview.md @@ -1,14 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Changesets -title: API Versioning for GraphQL-Ruby -desc: Evolve your schema over time, feature-by-feature -index: 0 ---- - +# API Versioning for GraphQL-Ruby Out-of-the-box, GraphQL is [versionless by design](https://graphql.org/learn/schema-design/). GraphQL's openness to extension paves the way for continuously expanding and improving an API. You can _always_ add new fields, new arguments, and new types to implement new features and customize existing behavior. @@ -47,6 +37,6 @@ Then, only clients requesting API versions _before_ this changeset would be abl To start using Changesets, read on: -- {% internal_link "Installing Changesets", "/changesets/installation" %} -- {% internal_link "Writing Changesets", "/changesets/definition" %} -- {% internal_link "Releasing Changesets", "/changesets/releases" %} +- [Installing Changesets](/changesets/installation) +- [Writing Changesets](/changesets/definition) +- [Releasing Changesets](/changesets/releases) diff --git a/guides/changesets/releases.md b/guides/changesets/releases.md index cba13722310..afe401b1a07 100644 --- a/guides/changesets/releases.md +++ b/guides/changesets/releases.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Changesets -title: Releasing Changesets -desc: Associating changes to version numbers -index: 3 ---- +# Releasing Changesets To be available to clients, Changesets added to the schema with `use GraphQL::Enterprise::Changeset::Release changesets_dir: "..."`: @@ -24,11 +15,11 @@ end This attaches each Changeset defined in `app/graphql/changesets/*.rb` to the schema. (It assumes Rails conventions, where an underscored file like `app/graphql/changesets/add_some_feature.rb` contains a class like `Changesets::AddSomeFeature`.) -{% callout warning %} +> **Warning:** +> +> Add `GraphQL::Enterprise::Changeset::Release` _before_ hooking up your root `query(...)`, `mutation(...)`, and `subscription(...)` types. Otherwise, the schema may not find links to types in new schema versions. +> -Add `GraphQL::Enterprise::Changeset::Release` _before_ hooking up your root `query(...)`, `mutation(...)`, and `subscription(...)` types. Otherwise, the schema may not find links to types in new schema versions. - -{% endcallout %} Alternatively, Changesets can be explicitly attached using `changesets: [...]`, for example: @@ -46,7 +37,7 @@ Only changesets in the directory (or in the array) will be shown to clients. The ## Inspecting Releases -To preview releases, you can create schema dumps by passing `context: { changeset_version: ... }` to {{ "Schema.to_definition" | api_doc }}. +To preview releases, you can create schema dumps by passing `context: { changeset_version: ... }` to [Schema.to_definition](rdoc-ref:GraphQL::Schema.to_definition). For example, to see how the schema looks with `API-Version: 2021-06-01`: @@ -56,7 +47,7 @@ schema_sdl = MyAppSchema.to_definition(context: { changeset_version: "2021-06-01 puts schema_sdl ``` -To make sure schema versions don't change unexpectedly, use the techniques described in the {% internal_link "Schema structure guide", "/testing/schema_structure" %}. +To make sure schema versions don't change unexpectedly, use the techniques described in the [Schema structure guide](/testing/schema_structure). ### Introspection Methods diff --git a/guides/css/main.scss b/guides/css/main.scss deleted file mode 100644 index a4d980d0416..00000000000 --- a/guides/css/main.scss +++ /dev/null @@ -1,747 +0,0 @@ ---- ---- - -@use "reset"; - -$brand-color: #a5152a; -$dark-theme-brand-color: #e5534b; -$brand-color-light: #ed8090; -$brand-color-extralight: #f9e8ee; -$dark-theme-brand-color-extralight: #262324; - -$experimental-color: #91812f; -$experimental-background: hsla(50, 100%, 32%, 0.15); - -$pro-color: #406db5; -$pro-background: hsla(217, 100%, 29%, 0.15); - -$enterprise-color: #238c44; -$enterprise-background: hsla(135, 97%, 25%, 0.15); - -$dark-theme-code-border: #aaaaaa; -$code-border: #d6d6d6; -$dark-theme-code-background: #1e1b1b; -$code-background: #fafafa; -$dark-theme-code-color: #b5b5b5; -$code-color: #777777; -$code-border-radius: 2px; - -$muted-color: #777777; -$subtle-color: #aaaaaa; -$font: 'Rubik', sans-serif; -$code-font: 'Monaco', monospace; - -$faint-color: #f0f0f0; -$dark-theme-faint-color: #6a6969; - -$font-color: black; -$dark-theme-font-color: #dbdbdb; - -$background-color: #fafafa; -$dark-theme-background-color: #422e2e; - -$container-color: white; -$dark-theme-container-color: #424242; - -body { - font-family: $font; - background: $background-color; - .dark-theme-button::after { - content: "☀" - } -} - -body.dark-theme { - background: $dark-theme-code-background; - color: $dark-theme-font-color; - .dark-theme-button::after { - content: "☽" - } -} - -strong, b { - font-weight: bold; -} - -// Algolia highlights: -.ais-Highlight { - font-style: normal; - font-weight: bold; -} - -.dark-theme { - .header { - background: $dark-theme-container-color; - box-shadow: 0px 0px 10px 0px black; - .nav a:hover { - color: $font-color; - background-color: $dark-theme-brand-color; - } - } -} -.header { - box-shadow: 0px 0px 10px 0px #d6d6d6; - z-index: 1; - position: relative; - background: $container-color; - .nav { - $height: 30px; - $margin: 10px; - display: flex; - flex-wrap: wrap; - align-items: center; - $fade-time: 0.2s; - - .nav-links { - margin-left: auto; - display: flex; - } - - .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; - transition: color $fade-time; - padding: $margin; - height: $height; - display: flex; - align-items: center; - text-decoration: none; - &:hover { - background-color: $brand-color; - color: white; - } - } - } -} - -.header-container { - margin: 0px 20px 0px 20px; -} - -.container { - max-width: 1200px; - margin: 0px auto; - padding: 10px 20px; - background: $container-color; - &.fullwidth { - max-width: 100%; - margin: 0px 20px 0px 20px; - } -} - -.dark-theme { - .container { - background: $dark-theme-container-color; - } -} - -.callout { - padding: 20px 20px 10px 20px; - margin: 20px; - border: 2px; - border-radius: 10px; - .heading { - font-size: 20px; - font-weight: bold; - margin-bottom: 20px; - } - - &.callout-warning { - background-color: rgba(255, 217, 0, 0.2); - border-color: rgba(255, 217, 0, 0.5); - } -} - -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; -} - -.dark-theme { - pre { - background-color: $dark-theme-code-background; - border: 1px solid $dark-theme-code-border; - } -} - -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; -} - -ol { - list-style: decimal; - margin-left: 5px; -} - -code { - font-family: $code-font; - color: $code-color; - font-weight: 400; -} -.dark-theme code { - color: $dark-theme-code-color; -} -.code .line-numbers { - display: none; -} - - -.dark-theme a { - color: $dark-theme-brand-color; - border-color: $dark-theme-brand-color; - code { - color: $dark-theme-brand-color; - } -} - -a { - color: $brand-color; - border-color: $brand-color; - text-decoration: none; - code { - color: $brand-color; - } -} - -a:hover, a:hover code { - text-decoration: underline; -} - -#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; -} - -.guides-toc { - ul { - list-style: none; - display: flex; - flex-wrap: wrap; - } - - li { - padding-bottom: 10px; - padding-right: 10px; - } -} - - -.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; - text-decoration: underline; - &:hover { - background-color: $color; - color: $background-color; - } - } -} - -.experimental-header { - @include doc-header($experimental-color, $experimental-background); -} - -.pro-header { - @include doc-header($pro-color, $pro-background); -} - -.enterprise-header { - @include doc-header($enterprise-color, $enterprise-background); -} - -.dark-theme .guide-footer { - background-color: $dark-theme-brand-color-extralight; -} - -.guide-footer { - background: $brand-color-extralight; - margin: 25px 0px 0px 0px; - padding: 10px; - border-radius: $code-border-radius; -} - - -.dark-theme { - .hero { - .hero-part { - &.shaded { - background: $dark-theme-faint-color; - } - - h2 { - color: $dark-theme-brand-color; - text-shadow: $dark-theme-background-color 1px 1px 1px; - } - } - } -} - -.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-subtitle { - padding: 10px 0px; - p { - margin: 5px auto; - text-align: center; - } - } - - .hero-part { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - - &.shaded { - background: $faint-color; - } - - h2 { - color: $brand-color; - text-shadow: #cccccc 1px 1px 1px; - font-size: 1.4em; - } - - .hero-feature { - padding: 15px; - flex-basis: calc(50% - 60px); - flex-grow: 1; - } - } -} - -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; - } -} - -.dark-theme { - .search-input { - background: $dark-theme-code-background; - color: $dark-theme-font-color; - } - .search-results-container { - background-color: $dark-theme-background-color; - #search-results { - .search-result { - &:focus, &:hover { - background-color: $dark-theme-container-color; - border-bottom-color: $dark-theme-brand-color; - .search-title { - color: $dark-theme-brand-color; - } - } - .search-category { - border: 1px solid $dark-theme-brand-color; - color: $dark-theme-brand-color; - } - } - } - } -} -.search-input { - font-size: 1em; - padding: 5px; - margin: 10px; - border: 1px solid $subtle-color; - border-radius: 3px; -} - -.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; - background-color: $bg-highlight; - border-bottom-color: $brand-color; - .search-title { - color: $brand-color; - } - } - } - } -} - -.dark-theme ul.breadcrumb .jump-to-select { - color: $dark-theme-brand-color; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='292.4' height='292.4'%3E%3Cpath fill='%23e5534b' d='M287 69.4a17.6 17.6 0 0 0-13-5.4H18.4c-5 0-9.3 1.8-12.9 5.4A17.6 17.6 0 0 0 0 82.2c0 5 1.8 9.3 5.4 12.9l128 127.9c3.6 3.6 7.8 5.4 12.8 5.4s9.2-1.8 12.8-5.4L287 95c3.5-3.5 5.4-7.8 5.4-12.8 0-5-1.9-9.2-5.5-12.8z'/%3E%3C/svg%3E"); -} - -ul.breadcrumb { - color: $muted-color; - - li { - display: inline; - list-style: none; - margin: 0; - } - li:before { - content: "»"; - margin: 0px 4px 0px 2px; - } - li:first-child:before { - content: ""; - margin: 0; - } - - .jump-to-select { - box-sizing: border-box; - -moz-appearance: none; - -webkit-appearance: none; - appearance: none; - padding: 5px 20px 5px 5px; - border: 1px solid $code-border; - border-radius: 5px; - background-color: transparent; - color: $brand-color; - font-size: 16px; - font-weight: 500; - line-height: 1.3; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='292.4' height='292.4'%3E%3Cpath fill='%23a5152a' d='M287 69.4a17.6 17.6 0 0 0-13-5.4H18.4c-5 0-9.3 1.8-12.9 5.4A17.6 17.6 0 0 0 0 82.2c0 5 1.8 9.3 5.4 12.9l128 127.9c3.6 3.6 7.8 5.4 12.8 5.4s9.2-1.8 12.8-5.4L287 95c3.5-3.5 5.4-7.8 5.4-12.8 0-5-1.9-9.2-5.5-12.8z'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right 8px center; - background-size: 9px; - - cursor: default; - - &:hover { - border-color: #777; - } - - &:focus { - border-color: #999; - box-shadow: 0 0 1px 2px #6db4ff; - outline: none; - } - - - option { - color: black; - } - } -} - - -.dark-theme { - .table-of-contents { - background: $dark-theme-code-background; - } -} - -.table-of-contents { - float: right; - border: 1px solid $subtle-color; - border-radius: 3px; - padding: 15px; - margin: 0 10px 10px 10px; - width: 300px; - background: $code-background; - .contents-header { - margin: 0 0 5px 20px; - } - .contents-list { - margin: 0; - list-style: decimal; - padding-left: 5px; - .contents-entry { - &::marker { - color: $muted-color; - } - - .contents-entry { - list-style: none; - } - } - } -} - -/* 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 */ - - -.dark-theme { - .highlight .hll { background-color: #49483e } - pre.highlight { background: #272822; color: #f8f8f2 } - .highlight .c { color: #75715e } /* Comment */ - .highlight .err { color: #960050; background-color: #1e0010 } /* Error */ - .highlight .k { color: #66d9ef } /* Keyword */ - .highlight .l { color: #ae81ff } /* Literal */ - .highlight .n { color: #f8f8f2 } /* Name */ - .highlight .o { color: #f92672 } /* Operator */ - .highlight .p { color: #f8f8f2 } /* Punctuation */ - .highlight .ch { color: #75715e } /* Comment.Hashbang */ - .highlight .cm { color: #75715e } /* Comment.Multiline */ - .highlight .cp { color: #75715e } /* Comment.Preproc */ - .highlight .cpf { color: #75715e } /* Comment.PreprocFile */ - .highlight .c1 { color: #75715e } /* Comment.Single */ - .highlight .cs { color: #75715e } /* Comment.Special */ - .highlight .gd { color: #f92672; background-color: #5e4343; } /* Generic.Deleted */ - .highlight .ge { font-style: italic } /* Generic.Emph */ - .highlight .gi { color: #a6e22e; background-color: #475547; } /* Generic.Inserted */ - .highlight .gs { font-weight: bold } /* Generic.Strong */ - .highlight .gu { color: #75715e } /* Generic.Subheading */ - .highlight .kc { color: #66d9ef } /* Keyword.Constant */ - .highlight .kd { color: #66d9ef } /* Keyword.Declaration */ - .highlight .kn { color: #f92672 } /* Keyword.Namespace */ - .highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ - .highlight .kr { color: #66d9ef } /* Keyword.Reserved */ - .highlight .kt { color: #66d9ef } /* Keyword.Type */ - .highlight .ld { color: #e6db74 } /* Literal.Date */ - .highlight .m { color: #ae81ff } /* Literal.Number */ - .highlight .s { color: #e6db74 } /* Literal.String */ - .highlight .na { color: #a6e22e } /* Name.Attribute */ - .highlight .nb { color: #f8f8f2 } /* Name.Builtin */ - .highlight .nc { color: #a6e22e } /* Name.Class */ - .highlight .no { color: #66d9ef } /* Name.Constant */ - .highlight .nd { color: #a6e22e } /* Name.Decorator */ - .highlight .ni { color: #f8f8f2 } /* Name.Entity */ - .highlight .ne { color: #a6e22e } /* Name.Exception */ - .highlight .nf { color: #a6e22e } /* Name.Function */ - .highlight .nl { color: #f8f8f2 } /* Name.Label */ - .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ - .highlight .nx { color: #a6e22e } /* Name.Other */ - .highlight .py { color: #f8f8f2 } /* Name.Property */ - .highlight .nt { color: #f92672 } /* Name.Tag */ - .highlight .nv { color: #f8f8f2 } /* Name.Variable */ - .highlight .ow { color: #f92672 } /* Operator.Word */ - .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ - .highlight .mb { color: #ae81ff } /* Literal.Number.Bin */ - .highlight .mf { color: #ae81ff } /* Literal.Number.Float */ - .highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ - .highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ - .highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ - .highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ - .highlight .sc { color: #e6db74 } /* Literal.String.Char */ - .highlight .sd { color: #e6db74 } /* Literal.String.Doc */ - .highlight .s2 { color: #e6db74 } /* Literal.String.Double */ - .highlight .se { color: #ae81ff } /* Literal.String.Escape */ - .highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ - .highlight .si { color: #e6db74 } /* Literal.String.Interpol */ - .highlight .sx { color: #e6db74 } /* Literal.String.Other */ - .highlight .sr { color: #e6db74 } /* Literal.String.Regex */ - .highlight .s1 { color: #e6db74 } /* Literal.String.Single */ - .highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ - .highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ - .highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ - .highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ - .highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ - .highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ -} diff --git a/guides/dataloader/adopting.md b/guides/dataloader/adopting.md index b7042b0e352..fa565d5ed0b 100644 --- a/guides/dataloader/adopting.md +++ b/guides/dataloader/adopting.md @@ -1,13 +1,6 @@ ---- -layout: guide -search: true -section: Dataloader -title: Dataloader vs. GraphQL-Batch -desc: Comparing and Contrasting Batch Loading Options -index: 3 ---- - -{{ "GraphQL::Dataloader" | api_doc }} solves the same problem as [`GraphQL::Batch`](https://github.com/shopify/graphql-batch). There are a few major differences between the modules: +# Dataloader vs. GraphQL-Batch + +[GraphQL::Dataloader](rdoc-ref:GraphQL::Dataloader) solves the same problem as [`GraphQL::Batch`](https://github.com/shopify/graphql-batch). There are a few major differences between the modules: - __Concurrency Primitive:__ GraphQL-Batch uses `Promise`s from [`promise.rb`](https://github.com/lgierth/promise.rb); GraphQL::Dataloader uses Ruby's [`Fiber` API](https://ruby-doc.org/core-3.0.0/Fiber.html). These primitives dictate how batch loading code is written (see below for comparisons). diff --git a/guides/dataloader/async_dataloader.md b/guides/dataloader/async_dataloader.md index f725602c9a2..1a7849c7371 100644 --- a/guides/dataloader/async_dataloader.md +++ b/guides/dataloader/async_dataloader.md @@ -1,13 +1,6 @@ ---- -layout: guide -search: true -section: Dataloader -title: Async Source Execution -desc: Using AsyncDataloader to fetch external data in parallel -index: 5 ---- +# Async Source Execution -`AsyncDataloader` will run {{ "GraphQL::Dataloader::Source#fetch" | api_doc }} calls in parallel, so that external service calls (like database queries or network calls) don't have to wait in a queue. +`AsyncDataloader` will run [GraphQL::Dataloader::Source#fetch](rdoc-ref:GraphQL::Dataloader::Source#fetch) calls in parallel, so that external service calls (like database queries or network calls) don't have to wait in a queue. To use `AsyncDataloader`, hook it up in your schema _instead of_ `GraphQL::Dataloader`: @@ -22,11 +15,11 @@ __Also__, add [the `async` gem](https://github.com/socketry/async) to your proje bundle add async ``` -Now, {{ "GraphQL::Dataloader::AsyncDataloader" | api_doc }} will create `Async::Task` instances instead of plain `Fiber`s and the `async` gem will manage parallelism. +Now, [GraphQL::Dataloader::AsyncDataloader](rdoc-ref:GraphQL::Dataloader::AsyncDataloader) will create `Async::Task` instances instead of plain `Fiber`s and the `async` gem will manage parallelism. For a demonstration of this behavior, see: [https://github.com/rmosolgo/rails-graphql-async-demo](https://github.com/rmosolgo/rails-graphql-async-demo) -_You can also implement {% internal_link "manual parallelism", "/dataloader/parallelism" %} using `dataloader.yield`._ +_You can also implement [manual parallelism](/dataloader/parallelism) using `dataloader.yield`._ ## Rails @@ -40,7 +33,7 @@ end ``` ### ActiveRecord Connections -You can use Dataloader's {% internal_link "Fiber lifecycle hooks", "/dataloader/dataloader#fiber-lifecycle-hooks" %} to improve ActiveRecord connection handling: +You can use Dataloader's [Fiber lifecycle hooks](/dataloader/dataloader#fiber-lifecycle-hooks) to improve ActiveRecord connection handling: - In Rails < 7.2, connections are not reused when a Fiber exits; instead, they're only reused when a request or background job finishes. You can add manual `release_connection` calls to improve this. - With `isolation_level = :fiber`, new Fibers don't inherit `connected_to ...` settings from their parent fibers. @@ -77,4 +70,4 @@ Modify the example according to your database configuration and abstract class h ## Other Options -You can also manually implement parallelism with Dataloader. See the {% internal_link "Dataloader Parallelism", "/dataloader/parallelism" %} guide for details. +You can also manually implement parallelism with Dataloader. See the [Dataloader Parallelism](/dataloader/parallelism) guide for details. diff --git a/guides/dataloader/dataloader.md b/guides/dataloader/dataloader.md index bde06aba0f4..d91eeb2b4b5 100644 --- a/guides/dataloader/dataloader.md +++ b/guides/dataloader/dataloader.md @@ -1,22 +1,15 @@ ---- -layout: guide -search: true -section: Dataloader -title: Dataloader -desc: The Dataloader orchestrates Fibers and Sources -index: 2 ---- - -{{ "GraphQL::Dataloader" | api_doc }} instances are created for each query (or multiplex) and they: - -- Cache {% internal_link "Source", "/dataloader/sources" %} instances for the duration of GraphQL execution +# Dataloader + +[GraphQL::Dataloader](rdoc-ref:GraphQL::Dataloader) instances are created for each query (or multiplex) and they: + +- Cache [Source](/dataloader/sources) instances for the duration of GraphQL execution - Run pending Fibers to resolve data requirements and continue GraphQL execution During a query, you can access the dataloader instance with: -- {{ "GraphQL::Query::Context#dataloader" | api_doc }} (`context.dataloader`, anywhere that query context is available) -- {{ "GraphQL::Schema::Object#dataloader" | api_doc }} (`dataloader` inside a resolver method) -- {{ "GraphQL::Schema::Resolver#dataloader" | api_doc }} (`dataloader` inside `def resolve` of a Resolver, Mutation, or Subscription class.) +- [GraphQL::Query::Context#dataloader](rdoc-ref:GraphQL::Query::Context#dataloader) (`context.dataloader`, anywhere that query context is available) +- [GraphQL::Schema::Object#dataloader](rdoc-ref:GraphQL::Schema::Object#dataloader) (`dataloader` inside a resolver method) +- [GraphQL::Schema::Resolver#dataloader](rdoc-ref:GraphQL::Schema::Resolver#dataloader) (`dataloader` inside `def resolve` of a Resolver, Mutation, or Subscription class.) ## Fiber Lifecycle Hooks @@ -37,6 +30,6 @@ Then, use your customized dataloader instead of the built-in one: end ``` -- __{{ "GraphQL::Dataloader#get_fiber_variables" | api_doc }}__ is called before creating a Fiber. By default, it returns a hash containing the parent Fiber's variables (from `Thread.current[...]`). You can add to this hash in your own implementation of this method. -- __{{ "GraphQL::Dataloader#set_fiber_variables" | api_doc }}__ is called inside the new Fiber. It's passed the hash returned from `get_fiber_variables`. You can use this method to initialize "global" state inside the new Fiber. -- __{{ "GraphQL::Dataloader#cleanup_fiber" | api_doc }}__ is called just before a Dataloader Fiber exits. You can use this methods to teardown any state that you prepared in `set_fiber_variables`. +- __[GraphQL::Dataloader#get_fiber_variables](rdoc-ref:GraphQL::Dataloader#get_fiber_variables)__ is called before creating a Fiber. By default, it returns a hash containing the parent Fiber's variables (from `Thread.current[...]`). You can add to this hash in your own implementation of this method. +- __[GraphQL::Dataloader#set_fiber_variables](rdoc-ref:GraphQL::Dataloader#set_fiber_variables)__ is called inside the new Fiber. It's passed the hash returned from `get_fiber_variables`. You can use this method to initialize "global" state inside the new Fiber. +- __[GraphQL::Dataloader#cleanup_fiber](rdoc-ref:GraphQL::Dataloader#cleanup_fiber)__ is called just before a Dataloader Fiber exits. You can use this methods to teardown any state that you prepared in `set_fiber_variables`. diff --git a/guides/dataloader/overview.md b/guides/dataloader/overview.md index e842ae94220..bd2ee009b89 100644 --- a/guides/dataloader/overview.md +++ b/guides/dataloader/overview.md @@ -1,13 +1,6 @@ ---- -layout: guide -search: true -section: Dataloader -title: Overview -desc: Getting started with the Fiber-based Dataloader -index: 0 ---- +# Overview - {{ "GraphQL::Dataloader" | api_doc }} provides efficient, batched access to external services, backed by Ruby's `Fiber` concurrency primitive. It has a per-query result cache and {% internal_link "AsyncDataloader", "/dataloader/async_dataloader" %} supports truly parallel execution out-of-the-box. + [GraphQL::Dataloader](rdoc-ref:GraphQL::Dataloader) provides efficient, batched access to external services, backed by Ruby's `Fiber` concurrency primitive. It has a per-query result cache and [AsyncDataloader](/dataloader/async_dataloader) supports truly parallel execution out-of-the-box. `GraphQL::Dataloader` is inspired by [`@bessey`'s proof-of-concept](https://github.com/bessey/graphql-fiber-test/tree/no-gem-changes) and [shopify/graphql-batch](https://github.com/shopify/graphql-batch). @@ -34,11 +27,11 @@ At a high level, `GraphQL::Dataloader`'s usage of `Fiber` looks like this: Whenever `GraphQL::Dataloader` creates a new `Fiber`, it copies each pair from `Thread.current[...]` and reassigns them inside the new `Fiber`. -`AsyncDataloader`, built on top of the [`async` gem](https://github.com/socketry/async), supports parallel I/O operations (like network and database communication) via Ruby's non-blocking `Fiber.schedule` API. {% internal_link "Learn more →", "/dataloader/async_dataloader" %}. +`AsyncDataloader`, built on top of the [`async` gem](https://github.com/socketry/async), supports parallel I/O operations (like network and database communication) via Ruby's non-blocking `Fiber.schedule` API. [Learn more →](/dataloader/async_dataloader). ## Getting Started -To install {{ "GraphQL::Dataloader" | api_doc }}, add it to your schema with `use ...`, for example: +To install [GraphQL::Dataloader](rdoc-ref:GraphQL::Dataloader), add it to your schema with `use ...`, for example: ```ruby class MySchema < GraphQL::Schema @@ -130,11 +123,11 @@ end ## Data Sources -To implement batch-loading data sources, see the {% internal_link "Sources guide", "/dataloader/sources" %}. +To implement batch-loading data sources, see the [Sources guide](/dataloader/sources). ## Parallelism You can run I/O operations in parallel with GraphQL::Dataloader. There are two approaches: -- `AsyncDataloader` uses the `async` gem to automatically background I/O from `Dataloader::Source#fetch` calls. {% internal_link "Read More", "/dataloader/async_dataloader" %} -- You can manually call `dataloader.yield` after starting work in the background. {% internal_link "Read More", "/dataloader/parallelism" %} +- `AsyncDataloader` uses the `async` gem to automatically background I/O from `Dataloader::Source#fetch` calls. [Read More](/dataloader/async_dataloader) +- You can manually call `dataloader.yield` after starting work in the background. [Read More](/dataloader/parallelism) diff --git a/guides/dataloader/parallelism.md b/guides/dataloader/parallelism.md index a0634bb7bf4..a4fabf351ad 100644 --- a/guides/dataloader/parallelism.md +++ b/guides/dataloader/parallelism.md @@ -1,13 +1,6 @@ ---- -layout: guide -search: true -section: Dataloader -title: Manual Parallelism -desc: Yield to Dataloader after starting work -index: 7 ---- +# Manual Parallelism -You can coordinate with {{ "GraphQL::Dataloader" | api_doc }} to run tasks in the background. To do this, call `dataloader.yield` inside `Source#fetch` after kicking off your task. For example: +You can coordinate with [GraphQL::Dataloader](rdoc-ref:GraphQL::Dataloader) to run tasks in the background. To do this, call `dataloader.yield` inside `Source#fetch` after kicking off your task. For example: ```ruby def fetch(ids) @@ -23,7 +16,7 @@ def fetch(ids) end ``` -_Alternatively, you can use {% internal_link "AsyncDataloader", "/dataloader/async_dataloader" %} to automatically background I/O inside `Source#fetch` calls._ +_Alternatively, you can use [AsyncDataloader](/dataloader/async_dataloader) to automatically background I/O inside `Source#fetch` calls._ ## Example: Rails load_async diff --git a/guides/dataloader/sources.md b/guides/dataloader/sources.md index 0dc4b03ddb8..56520c670d3 100644 --- a/guides/dataloader/sources.md +++ b/guides/dataloader/sources.md @@ -1,13 +1,6 @@ ---- -layout: guide -search: true -section: Dataloader -title: Sources -desc: Batch-loading objects for GraphQL::Dataloader -index: 1 ---- +# Sources -_Sources_ are what {{ "GraphQL::Dataloader" | api_doc }} uses to fetch data from external services. +_Sources_ are what [GraphQL::Dataloader](rdoc-ref:GraphQL::Dataloader) uses to fetch data from external services. ## Source Concepts @@ -27,7 +20,7 @@ Sources will receive two kinds of inputs from `GraphQL::Dataloader`: (`dataloader.with(source_class, *batch_parameters)` returns an instance of `source_class` with the given batch parameters -- but it might be an instance which was cached by `dataloader`.) - Additionally, batch parameters are used to de-duplicate Source initializations during a query run. `.with(...)` calls that have the same batch parameters will use the same Source instance under the hood. To customize how Sources are de-duplicated, see {{ "GraphQL::Dataloader::Source.batch_key_for" | api_doc }}. + Additionally, batch parameters are used to de-duplicate Source initializations during a query run. `.with(...)` calls that have the same batch parameters will use the same Source instance under the hood. To customize how Sources are de-duplicated, see [GraphQL::Dataloader::Source.batch_key_for](rdoc-ref:GraphQL::Dataloader::Source.batch_key_for). ## Example: Loading Strings from Redis by Key @@ -132,11 +125,11 @@ def fetch(keys) end ``` -See the {% internal_link "parallelism guide", "/dataloader/parallelism" %} for details about this approach. +See the [parallelism guide](/dataloader/parallelism) for details about this approach. ## Filling the Dataloader Cache -If you load records from the database, you can use them to populate a source's cache by using {{ "Dataloader::Source#merge" | api_doc }}. For example: +If you load records from the database, you can use them to populate a source's cache by using [Dataloader::Source#merge](rdoc-ref:GraphQL::Dataloader::Source#merge). For example: ```ruby # Build a `{ key => value }` map to populate the cache diff --git a/guides/dataloader/testing.md b/guides/dataloader/testing.md index 5742c6b897b..f339bc686d6 100644 --- a/guides/dataloader/testing.md +++ b/guides/dataloader/testing.md @@ -1,13 +1,6 @@ ---- -layout: guide -search: true -section: Dataloader -title: Testing -desc: Tips for testing Dataloader implementation -index: 4 ---- +# Testing -There are a few techniques for testing your {{ "GraphQL::Dataloader" | api_doc }} setup. +There are a few techniques for testing your [GraphQL::Dataloader](rdoc-ref:GraphQL::Dataloader) setup. ## Integration Tests @@ -43,7 +36,7 @@ You could also make specific assertions on the queries that are run (see the [`s ## Testing Dataloader Sources -You can also test `Dataloader` behavior outside of GraphQL using {{ "GraphQL::Dataloader.with_dataloading" | api_doc }}. For example, let's say you have a `Sources::ActiveRecord` source defined like so: +You can also test `Dataloader` behavior outside of GraphQL using [GraphQL::Dataloader.with_dataloading](rdoc-ref:GraphQL::Dataloader.with_dataloading). For example, let's say you have a `Sources::ActiveRecord` source defined like so: ```ruby diff --git a/guides/defer/graphiql.md b/guides/defer/graphiql.md index 661abeeabfa..b40e1db125e 100644 --- a/guides/defer/graphiql.md +++ b/guides/defer/graphiql.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: GraphQL Pro - Defer -title: Use with GraphiQL -desc: Using @defer with the GraphiQL IDE -index: 4 -pro: true ---- +# Use with GraphiQL You can use `@defer` and `@stream` with [GraphiQL](https://github.com/graphql/graphiql/blob/main/packages/graphiql/README.md), an in-browser IDE. diff --git a/guides/defer/overview.md b/guides/defer/overview.md index 0b58e7b915d..2fc9abe1db4 100644 --- a/guides/defer/overview.md +++ b/guides/defer/overview.md @@ -1,21 +1,12 @@ ---- -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. +# Overview + +`@defer` is a [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). -`@stream` is like `@defer`, but it returns list items one at a time. Find details in the {% internal_link "Stream guide", "/defer/stream" %}. +`@stream` is like `@defer`, but it returns list items one at a time. Find details in the [Stream guide](/defer/stream). ## Example @@ -25,7 +16,7 @@ In this example, the local server maintains an index of items ("decks"), but the 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" }} +![Rails without defer](https://user-images.githubusercontent.com/2231765/53442028-4a122b00-39d6-11e9-8e33-b91791bf3b98.gif) But, we can add `@defer` to slow fields: @@ -44,7 +35,7 @@ But, we can add `@defer` to slow fields: 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" }} +![Rails with defer](https://user-images.githubusercontent.com/2231765/53442027-4a122b00-39d6-11e9-8d7b-feb7a4f7962a.gif) This way, clients get a snappy feel from the app even while data is still loading. @@ -58,4 +49,4 @@ View the full demo at https://github.com/rmosolgo/graphql_defer_example. ## Next Steps -{% internal_link "Set up your server", "/defer/setup" %} to support `@defer` or read about {% internal_link "client usage", "/defer/usage" %} of it. +[Set up your server](/defer/setup) to support `@defer` or read about [client usage](/defer/usage) of it. diff --git a/guides/defer/setup.md b/guides/defer/setup.md index fc8895c3bf7..1c600e5004e 100644 --- a/guides/defer/setup.md +++ b/guides/defer/setup.md @@ -1,13 +1,4 @@ ---- -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 ---- +# Server Setup Before using `@defer` in queries, you have to: @@ -45,7 +36,7 @@ end This will: -- Attach a {% internal_link "custom directive", "/type_definitions/directives" %} called `@defer` +- Attach a [custom directive](/type_definitions/directives) called `@defer` - Add instrumentation to queries to track deferred work and execute it later ## Sending streaming responses @@ -58,7 +49,7 @@ Many web frameworks have support for streaming responses, for example: 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`. +To investigate support with a web framework, please [open an issue](https://github.com/rmosolgo/graphql-ruby/issues/new?title=Server+support+for+%40defer+with+...&body=) or email `support@graphql.pro`. ### Checking for deferrals @@ -166,4 +157,4 @@ use Directives::Defer ``` ## Next Steps -Read about {% internal_link "client usage", "/defer/usage" %} of `@defer`. +Read about [client usage](/defer/usage) of `@defer`. diff --git a/guides/defer/stream.md b/guides/defer/stream.md index 6dd3d1ac150..6c3f5999696 100644 --- a/guides/defer/stream.md +++ b/guides/defer/stream.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: GraphQL Pro - Defer -title: Stream -desc: Using @stream to receive list items one at a time -index: 3 -pro: true ---- +# Stream `@stream` works very much like `@defer`, except it only applies to list fields. When a field has `@stream` and it returns a list, then each item in the list is returned to the client as a patch. `@stream` is described in a [proposal to the GraphQL specification](https://github.com/graphql/graphql-wg/blob/main/rfcs/DeferStream.md). @@ -24,7 +15,7 @@ class MySchema < GraphQL::Schema end ``` -Additionally, you should update your controller to handle deferred parts of the response. See the {% internal_link "@defer setup guide", "defer/setup#sending-streaming-responses" %} for details. (`@stream` uses the same deferral pipeline as `@defer`, so the same setup instructions apply.) +Additionally, you should update your controller to handle deferred parts of the response. See the [@defer setup guide](/defer/setup#sending-streaming-responses) for details. (`@stream` uses the same deferral pipeline as `@defer`, so the same setup instructions apply.) ## Usage diff --git a/guides/defer/usage.md b/guides/defer/usage.md index 92925da3fec..46a9da50890 100644 --- a/guides/defer/usage.md +++ b/guides/defer/usage.md @@ -1,14 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: GraphQL Pro - Defer -title: Usage -desc: Using @defer on the client side -index: 2 -pro: true ---- - +# Usage `@defer` is a [GraphQL directive](https://graphql.org/learn/queries/#directives) which instructs the server to execute the field in a special way: @@ -28,8 +18,8 @@ Apollo-Client [currently supports the @defer directive](https://www.apollographq `@defer` also accepts a `label:` option which will be included in outgoing patches when it's present in the query (eg, `@defer(label: "patch1")`). -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! +Want to use `@defer` with another client? Please [open an issue](https://github.com/rmosolgo/graphql-ruby/issues/new?title=Client+support+for+%40defer+with+...&body=) or email `support@graphql.pro` and we'll dig in! ## Next Steps -{% internal_link "Set up your server", "/defer/setup" %} to support `@defer`. +[Set up your server](/defer/setup) to support `@defer`. diff --git a/guides/development.md b/guides/development.md index 935ab067800..b0d572460fb 100644 --- a/guides/development.md +++ b/guides/development.md @@ -1,23 +1,16 @@ ---- -layout: guide -doc_stub: false -search: true -title: Development -section: Other -desc: Hacking on GraphQL Ruby ---- +# Development 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 +- [Setup your development environment](#setup) +- [Run the tests to verify your setup](#running-the-tests) +- [Debug with pry](#debugging-with-pry) +- [Run the benchmarks to test performance in your environment](#running-the-benchmarks) +- [Coding guidelines for working on your contribution](#coding-guidelines) - Special tools for building the lexer and parser -- Building and publishing the [GraphQL Ruby website](#website) -- [Versioning](#versioning) describes how changes are managed and released -- [Releasing](#releasing) Gem versions +- [Building and publishing the GraphQL Ruby website](#website) +- [Versioning describes how changes are managed and released](#versioning) +- [Releasing Gem versions](#releasing) ## Setup @@ -167,54 +160,32 @@ Don't fret about coding style or organization. There's a minimal Rubocop config To update the website, update the `.md` files in `guides/`. -To preview your changes, you can serve the website locally: +Install the optional documentation dependencies and build the site locally: +```sh +BUNDLE_WITH=docs bundle install +bundle exec rake docs:build +bundle exec rake docs:rdoc:serve ``` -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: +Then visit `http://127.0.0.1:8808`. Run the documentation checks before submitting a change: -``` -$ export ALGOLIA_API_KEY=... +```sh +bundle exec rake docs:check +bundle exec rake docs:build_twice ``` -Without this key, the search index will fall out-of-sync with the website. Contact @rmosolgo to gain access to this key. +See the [documentation maintenance guide](/docs/maintenance) for API links, GraphQL code blocks, redirects, and release documentation. ### 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: +The GraphQL-Ruby website has a rendered version of each published gem's API docs. Generate a local copy with: ``` -$ bundle exec rake site:publish +$ bundle exec rake "docs:rdoc:build_version[1.8.0]" ``` -Finally, check your work by visiting the docs on the website. +The output is written to `tmp/rdoc-api/1.8.0/`; the release workflow publishes it under `/api-doc/1.8.0/` while preserving older versions. ## Versioning diff --git a/guides/errors/error_handling.md b/guides/errors/error_handling.md index 0b49bb31221..ce254f36f87 100644 --- a/guides/errors/error_handling.md +++ b/guides/errors/error_handling.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Errors -title: Error Handling -desc: Rescuing application errors from field resolvers -index: 3 ---- +# Error Handling You can configure your schema to rescue application errors during field resolution. Errors during batch loading will also be rescued. @@ -40,11 +32,11 @@ The handler is called with several arguments: - __`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 +- __`field`__ is the [GraphQL::Schema::Field](rdoc-ref:GraphQL::Schema::Field) 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 +- Raise a GraphQL-friendly [GraphQL::ExecutionError](rdoc-ref:GraphQL::ExecutionError) 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) diff --git a/guides/errors/execution_errors.md b/guides/errors/execution_errors.md index 8f5cc0c974c..75943454d04 100644 --- a/guides/errors/execution_errors.md +++ b/guides/errors/execution_errors.md @@ -1,13 +1,3 @@ ---- -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 @@ -31,7 +21,7 @@ In general, top-level errors should only be used for exceptional circumstances w 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" %}. +When you want to notify a client some kind of recoverable issue, consider making error messages part of the schema, for example, as in [mutation errors](/mutations/mutation_errors). ## Adding Errors to the Array diff --git a/guides/errors/overview.md b/guides/errors/overview.md index c15fbc93f19..3e94ec20ef9 100644 --- a/guides/errors/overview.md +++ b/guides/errors/overview.md @@ -1,15 +1,4 @@ ---- -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/ ---- +# Errors in GraphQL 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. @@ -27,11 +16,11 @@ 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. -You can configure your schema to stop validating after a certain number of errors by setting {{ "Schema.validate_max_errors" | api_doc }}. Also, you can add a timeout to this step with {{ "Schema.validate_timeout" | api_doc }}. +You can configure your schema to stop validating after a certain number of errors by setting [Schema.validate_max_errors](rdoc-ref:GraphQL::Schema.validate_max_errors). Also, you can add a timeout to this step with [Schema.validate_timeout](rdoc-ref:GraphQL::Schema.validate_timeout). ## 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-Ruby supports pre-execution analysis, which may return `"errors"` instead of running a query. You can find details in the [Analysis guide](/queries/ast_analysis). ## GraphQL Invariants @@ -40,17 +29,17 @@ While GraphQL-Ruby is executing a query, some constraints must be satisfied. For - 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" %}. +These constraints are part of the GraphQL specification, and when they are violated, it must be addressed somehow. Read more in [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" %}. +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 [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" %}. +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 [Error Handling guide](/errors/error_handling). ## Unhandled Errors (Crashes) @@ -62,4 +51,4 @@ For example, Rails will probably return a generic `500` page. 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.html#errors-as-data" %} +For more about this approach, see [Mutation Errors](/mutations/mutation_errors.html#errors-as-data) diff --git a/guides/errors/type_errors.md b/guides/errors/type_errors.md index 0edc12c62b3..4eb64ddd5fa 100644 --- a/guides/errors/type_errors.md +++ b/guides/errors/type_errors.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Errors -title: Type Errors -desc: Handling type errors -index: 3 ---- +# Type Errors 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. @@ -15,7 +7,7 @@ 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: +You can specify behavior in these cases by defining a [Schema.type_error](rdoc-ref:GraphQL::Schema.type_error) hook: ```ruby class MySchema < GraphQL::Schema @@ -25,11 +17,11 @@ class MySchema < GraphQL::Schema 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 }}). +It is called with an instance of [GraphQL::UnresolvedTypeError](rdoc-ref:GraphQL::UnresolvedTypeError) or [GraphQL::InvalidNullError](rdoc-ref:GraphQL::InvalidNullError) and the query context (a [GraphQL::Query::Context](rdoc-ref:GraphQL::Query::Context)). 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 }} +- Unresolved Union / Interface types raise [GraphQL::UnresolvedTypeError](rdoc-ref:GraphQL::UnresolvedTypeError) An object that fails type resolution is treated as `nil`. diff --git a/guides/execution/migration.md b/guides/execution/migration.md index 3c7dea561d5..f129bf1098f 100644 --- a/guides/execution/migration.md +++ b/guides/execution/migration.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Execution -title: Migrating to Execution::Next -desc: Guidelines for migrating to the new execution engine -index: 2 ---- +# Migrating to Execution::Next This guide includes tips for migrating your schema configuration and production traffic to the new engine. @@ -14,7 +6,7 @@ This guide includes tips for migrating your schema configuration and production `Execution::Next` is designed to run alongside the previous engine so that the same schema can run queries _both_ ways. This supports an incremental migration and live toggling in production. -First, update your schema to include the necessary {% internal_link "field configurations", "/execution/next#field-configurations" %}. If you implement new class methods in your Object type classes, you can also migrate instance methods to call "up" to those class methods, preserving a single source of truth: +First, update your schema to include the necessary [field configurations](/execution/next#field-configurations). If you implement new class methods in your Object type classes, you can also migrate instance methods to call "up" to those class methods, preserving a single source of truth: ```ruby field :unpublished_posts, [Types::Post], resolve_each: true @@ -207,7 +199,7 @@ Visibility works exactly as before; both runtime modules call the same methods t ### Dataloader -Dataloader runs with new execution, but when migrating from instance methods to batch-level class methods, you may need to use {{ "Schema::Member::HasDataloader#dataload_all" | api_doc }} instead of `.dataload`. +Dataloader runs with new execution, but when migrating from instance methods to batch-level class methods, you may need to use [Schema::Member::HasDataloader#dataload_all](rdoc-ref:GraphQL::Schema::Member::HasDataloader#dataload_all) instead of `.dataload`. ### Tracing diff --git a/guides/execution/next.md b/guides/execution/next.md index 28dcc86fd0c..0e60005eee8 100644 --- a/guides/execution/next.md +++ b/guides/execution/next.md @@ -1,14 +1,6 @@ ---- -layout: guide -doc_stub: false -search: true -section: Execution -title: New Execution Module -desc: Background on GraphQL-Ruby's new execution approach -index: 1 ---- - -GraphQL-Ruby has a new execution engine, {{ "GraphQL::Execution::Next" | api_doc }}. It's much faster and less memory-consuming than the existing execution engine, but requires some care in migrating. +# New Execution Module + +GraphQL-Ruby has a new execution engine, [GraphQL::Execution::Next](rdoc-ref:GraphQL::Execution::Next). It's much faster and less memory-consuming than the existing execution engine, but requires some care in migrating. This feature is in heavy development, so if you give it a try and run into any problems, please open an issue on GitHub! @@ -37,7 +29,7 @@ The new execution engine is enabled with two steps: - Add the plugin to your schema with `use GraphQL::Execution::Next` - Call `MySchema.execute_next(...)` instead of `MySchema.execute(...)`. It takes the same arguments. -See {% internal_link "compatibility notes", "/execution/migration#compatibility-notes" %} for updating your schema to run queries with the new engine. +See [compatibility notes](/execution/migration#compatibility-notes) for updating your schema to run queries with the new engine. You can also add `..., as_default: true` to use `execute_next` by default. In that case, call `execute_legacy` if you need the old runtime. @@ -188,7 +180,7 @@ class Types::CommentType #### Rails Associations -Load ActiveRecord associations using {{ "GraphQL::Dataloader::ActiveRecordAssociationSource" | api_doc }}: +Load ActiveRecord associations using [GraphQL::Dataloader::ActiveRecordAssociationSource](rdoc-ref:GraphQL::Dataloader::ActiveRecordAssociationSource): ```ruby class Types::CommentType < Types::BaseObject @@ -201,7 +193,7 @@ end #### Rails Records -Load ActiveRecord associations using {{ "GraphQL::Dataloader::ActiveRecordSource" | api_doc }}. +Load ActiveRecord associations using [GraphQL::Dataloader::ActiveRecordSource](rdoc-ref:GraphQL::Dataloader::ActiveRecordSource). ```ruby class Types::SearchResult < Types::BaseObject @@ -248,4 +240,4 @@ end ## Migration -Read about migrating in the {% internal_link "Migration Doc", "/execution/migration" %}. +Read about migrating in the [Migration Doc](/execution/migration). diff --git a/guides/faq.md b/guides/faq.md index eab9a195531..98baee2ab45 100644 --- a/guides/faq.md +++ b/guides/faq.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -title: FAQ -other: true -desc: How to do common tasks ---- - +# FAQ Returning Route URLs ==================== diff --git a/guides/fields/arguments.md b/guides/fields/arguments.md index a6f38a7c191..8e2e0f646ae 100644 --- a/guides/fields/arguments.md +++ b/guides/fields/arguments.md @@ -1,176 +1,13 @@ ---- -layout: guide -doc_stub: false -search: true -section: Fields -title: Arguments -desc: Fields may take arguments as inputs -index: 1 ---- +# Arguments -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 part of the GraphQL::Schema::Argument API. The complete +reference, including nullability, defaults, deprecation, aliasing, +preprocessing, camelization, and supported input types, is maintained with +the implementation: -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: +[GraphQL::Schema::Argument](rdoc-ref:GraphQL::Schema::Argument) -```ruby -field :search_posts, [PostType], null: false do - argument :category, String -end - -def search_posts(category:) - Post.where(category: category).limit(10) -end -``` - -## Nullability - -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 -``` - -### Default Values - -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 -``` - -Arguments with `required: false` _do_ accept `null` as inputs from clients. This can be surprising in resolver code, for example, an argument with `Integer, required: false` can sometimes be `nil`. In this case, you can use `replace_null_with_default: true` to apply the given `default_value: ...` when clients provide `null`. For example: - -```ruby -# Even if clients send `query: null`, the resolver will receive `"*"` for this argument: -argument :query, String, required: false, default_value: "*", replace_null_with_default: true -``` - -Finally, `required: :nullable` will require clients to pass the argument, although it will accept `null` as a valid input. For example: - -```ruby -# This argument _must_ be given -- send `null` if there's no other appropriate value: -argument :email_address, String, required: :nullable -``` - - -## Deprecation - -**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 -``` - -## Aliasing - -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, as: :id -end - -def post(id:) - Post.find(id) -end -``` - -## Preprocessing - -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, 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 -``` - -## Automatic camelization - -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 -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, 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 -end - -def posts(start_year:) - # ... -end -``` - -## Valid Argument Types - -Only certain types are valid for arguments: - -- {{ "GraphQL::Schema::Scalar" | api_doc }}, including built-in scalars (string, int, float, boolean, ID) -- {{ "GraphQL::Schema::Enum" | api_doc }} -- {{ "GraphQL::Schema::InputObject" | api_doc }}, which allows key-value pairs as input -- {{ "GraphQL::Schema::List" | api_doc }}s of a valid input type, configured using `[...]` -- {{ "GraphQL::Schema::NonNull" | api_doc }}s of a valid input type (arguments are non-null by default; use `required: false` to make optional arguments) +Use that API reference for the exact argument(...) parameters and runtime +behavior. This guide remains the entry point for field arguments and connects +the topic to the [fields](/fields/introduction) and +[validation](/fields/validation) guides. diff --git a/guides/fields/introduction.md b/guides/fields/introduction.md index c84fd8d3d9e..0ab2d252a3f 100644 --- a/guides/fields/introduction.md +++ b/guides/fields/introduction.md @@ -1,275 +1,41 @@ ---- -layout: guide -doc_stub: false -search: true -section: Fields -title: Introduction -desc: Implement fields and resolvers with the Ruby DSL -index: 0 ---- +# Field definitions +Field definitions are part of the GraphQL::Schema::Field API. The complete +reference, including names, return types, descriptions, resolution behavior, +arguments, extras, and default options, is maintained with the implementation: -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: +[GraphQL::Schema::Field](rdoc-ref:GraphQL::Schema::Field) -```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: - -- [Names](#field-names) identify the field in GraphQL -- [Return types](#field-return-type) say what kind of data this field returns -- [Documentation](#field-documentation) includes description, comments 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) +Use that API reference when you need the exact field(...) parameters and +runtime behavior. This guide remains the entry point for the fields section +and links to the related [objects](/type_definitions/objects), +[interfaces](/type_definitions/interfaces), and +[arguments](/fields/arguments) guides. ## Field Names -A field's name is provided as the first argument or as the `name:` option: - -```ruby -field :team_captain, ... -# or: -field ..., name: :team_captain -``` - -Under the hood, GraphQL-Ruby **camelizes** field names, so `field :team_captain, ...` would be `{ teamCaptain }` in GraphQL. You can disable this behavior by adding `camelize: false` to your field definition or to the [default field options](#field-parameter-default-values). - -The field's name is also used as the basis of [field resolution](#field-resolution). +See the [Field API reference](rdoc-ref:GraphQL::Schema::Field). ## 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 `null:` keyword: - -- `null: true` (default) 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 # `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] # `[Int]`, may return a list or `nil`, the list may contain a mix of `Integer`s and `nil`s -``` +See the [Field API reference](rdoc-ref:GraphQL::Schema::Field). ## Field Documentation -Fields may be documented with a __description__, __comment__ 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 -``` - -__Comments__ can be added with the `field(...)` method as a keyword argument, or inside the block: -```ruby -# `comment:` keyword -field :name, String, null: false, comment: "Rename to full name" - -# inside the block -field :name, String, null: false do - comment "Rename to full name" -end -``` - -Generates field name with comment above "Rename to full name" above. - -```graphql -type Foo { - # Rename to full name - name: String! -} -``` - -__Deprecated__ fields can be marked by adding a `deprecation_reason:` keyword argument: - -```ruby -field :email, String, - deprecation_reason: "Users may have multiple emails, use `User.emails` instead." -``` - -Fields with a `deprecation_reason:` will appear as "deprecated" in GraphiQL. +See the [Field API reference](rdoc-ref:GraphQL::Schema::Field). ## 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. -- An optional `:fallback_value` can be supplied that will be used if the above fail. - -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(s) with the `hash_key:` or `dig:` 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" - -# Use the `#dig` method on the hash with `:nested` and `:movies` keys -field :movies, [Movie], null: false, - dig: [:nested, :movies] -``` - -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`. +See the [Field API reference](rdoc-ref:GraphQL::Schema::Field). ## 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" %} +See the [Field API reference](rdoc-ref:GraphQL::Schema::Field). ## 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` (Interpreter only), an instance of {{ "GraphQL::Execution::Interpreter::Arguments" | api_doc }} with argument metadata -- `parent` (the previous `object` in the query) -- 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. +See the [Field API reference](rdoc-ref:GraphQL::Schema::Field). ## 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 -``` - -To use `CustomField` in your Objects and Interfaces, you'll need to register it as a `field_class` on those classes. See [Customizing Fields](https://graphql-ruby.org/type_definitions/extensions#customizing-fields) for more information on how to do so. +See the [Field API reference](rdoc-ref:GraphQL::Schema::Field). diff --git a/guides/fields/limits.md b/guides/fields/limits.md index 0f03eb9bd86..cae85440e78 100644 --- a/guides/fields/limits.md +++ b/guides/fields/limits.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Fields -title: Limits -desc: Always limit lists of items -index: 4 ---- +# Limits ## List Fields @@ -27,4 +19,4 @@ This way, you won't hit your database for 1000 items! ## Connections -Connections accept a {% internal_link "`max_page_size` option","/pagination/using_connections#max-page-size" %} which limits the number of nodes. +Connections accept a [`max_page_size` option](/pagination/using_connections#max-page-size) which limits the number of nodes. diff --git a/guides/fields/resolvers.md b/guides/fields/resolvers.md index 4ba1320b984..2da15a09a31 100644 --- a/guides/fields/resolvers.md +++ b/guides/fields/resolvers.md @@ -1,23 +1,13 @@ ---- -layout: guide -doc_stub: false -search: true -section: Fields -title: Resolvers -desc: Reusable, extendable resolution logic for complex fields -index: 2 -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: +# Resolvers + +A [GraphQL::Schema::Resolver](rdoc-ref:GraphQL::Schema::Resolver) 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`. +Under the hood, [GraphQL::Schema::Mutation](rdoc-ref:GraphQL::Schema::Mutation) is a specialized subclass of `Resolver`. ## First, ask yourself ... @@ -88,7 +78,7 @@ class Types::User < BaseObject 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. +- If the module approach looks good to you, also consider [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? diff --git a/guides/fields/validation.md b/guides/fields/validation.md index 3eb860c661e..7f0d06a027f 100644 --- a/guides/fields/validation.md +++ b/guides/fields/validation.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Fields -title: Validation -desc: Rails-like validations for arguments -index: 3 ---- +# Validation Arguments can be validated at runtime using built-in or custom validators. @@ -36,15 +28,15 @@ Validations can be provided with a keyword (`validates: { ... }`) or with a meth See each validator's API docs for details: -- `length: { maximum: ..., minimum: ..., is: ..., within: ... }` {{ "Schema::Validator::LengthValidator" | api_doc }} -- `format: { with: /.../, without: /.../ }` {{ "Schema::Validator::FormatValidator" | api_doc }} -- `numericality: { greater_than:, greater_than_or_equal_to:, less_than:, less_than_or_equal_to:, other_than:, odd:, even: }` {{ "Schema::Validator::NumericalityValidator" | api_doc }} -- `inclusion: { in: [...] }` {{ "Schema::Validator::InclusionValidator" | api_doc }} -- `exclusion: { in: [...] }` {{ "Schema::Validator::ExclusionValidator" | api_doc }} -- `required: { one_of: [...] }` {{ "Schema::Validator::RequiredValidator" | api_doc }} -- `allow_blank: true|false` {{ "Schema::Validator::AllowBlankValidator" | api_doc }} -- `allow_null: true|false` {{ "Schema::Validator::AllowNullValidator" | api_doc }} -- `all: { ... }` {{ "Schema::Validator::AllValidator" | api_doc }} +- `length: { maximum: ..., minimum: ..., is: ..., within: ... }` [Schema::Validator::LengthValidator](rdoc-ref:GraphQL::Schema::Validator::LengthValidator) +- `format: { with: /.../, without: /.../ }` [Schema::Validator::FormatValidator](rdoc-ref:GraphQL::Schema::Validator::FormatValidator) +- `numericality: { greater_than:, greater_than_or_equal_to:, less_than:, less_than_or_equal_to:, other_than:, odd:, even: }` [Schema::Validator::NumericalityValidator](rdoc-ref:GraphQL::Schema::Validator::NumericalityValidator) +- `inclusion: { in: [...] }` [Schema::Validator::InclusionValidator](rdoc-ref:GraphQL::Schema::Validator::InclusionValidator) +- `exclusion: { in: [...] }` [Schema::Validator::ExclusionValidator](rdoc-ref:GraphQL::Schema::Validator::ExclusionValidator) +- `required: { one_of: [...] }` [Schema::Validator::RequiredValidator](rdoc-ref:GraphQL::Schema::Validator::RequiredValidator) +- `allow_blank: true|false` [Schema::Validator::AllowBlankValidator](rdoc-ref:GraphQL::Schema::Validator::AllowBlankValidator) +- `allow_null: true|false` [Schema::Validator::AllowNullValidator](rdoc-ref:GraphQL::Schema::Validator::AllowNullValidator) +- `all: { ... }` [Schema::Validator::AllValidator](rdoc-ref:GraphQL::Schema::Validator::AllValidator) Some of the validators accept customizable messages for certain validation failures; see the API docs for examples. diff --git a/guides/getting_started.md b/guides/getting_started.md index 99ce10049bd..edc22d1bd1b 100644 --- a/guides/getting_started.md +++ b/guides/getting_started.md @@ -1,11 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -title: Getting Started -section: Other -desc: Start here! ---- +# Getting Started ## Installation @@ -106,7 +99,7 @@ class Schema < GraphQL::Schema end ``` -This schema is ready to serve GraphQL queries! {% internal_link "Browse the guides","/guides" %} to learn about other GraphQL Ruby features. +This schema is ready to serve GraphQL queries! [Browse the guides](/guides) to learn about other GraphQL Ruby features. ### Execute queries @@ -133,14 +126,14 @@ result_hash = Schema.execute(query_string) # } ``` -See {% internal_link "Executing Queries","/queries/executing_queries" %} for more information about running queries on your schema. +See [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 {% internal_link "Connection guide", "/pagination/connection_concepts" %}, {% internal_link "Mutation guide", "mutations/mutation_classes" %}, and {% internal_link "Object Identification guide", "/schema/object_identification" %}. +- Relay-specific helpers for GraphQL, see the [Connection guide](/pagination/connection_concepts), [Mutation guide](/mutations/mutation_classes), and [Object Identification guide](/schema/object_identification). ## Use with Apollo Client diff --git a/guides/guides.html b/guides/guides.html deleted file mode 100644 index f221039a739..00000000000 --- a/guides/guides.html +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: Guides Index -sections: - - name: Schema - - name: Queries - - name: Execution - - name: Type Definitions - - name: Authorization - - name: Fields - - name: Mutations - - name: Errors - - name: Pagination - - name: Relay - - name: Dataloader - - name: Subscriptions - - name: GraphQL Pro - - name: GraphQL Pro - OperationStore - - name: GraphQL Pro - Defer - - name: GraphQL Enterprise - Rate Limiters - - name: GraphQL Enterprise - Object Cache - - name: GraphQL Enterprise - Changesets - - 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 }}

- -
- {% endfor %} -
diff --git a/guides/index.html b/guides/index.html deleted file mode 100644 index 39d666de7c4..00000000000 --- a/guides/index.html +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: Welcome -fullwidth: false ---- -
-
- GraphQL Ruby Logo -

GraphQL Ruby

-
-
-

The graphql gem implements the GraphQL Server Specification in Ruby.

-

Use it to add a GraphQL API to your Ruby or Rails app.

-
-
-
-

Install the Gem

-

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

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

Define Your Schema

-

- Describe your application with a - 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 -end -{% endhighlight %} -
-
-
-
-

Serve Queries

-

- Provide custom data to clients and extend your API with - {% internal_link "mutations", "/mutations/mutation_root" %}, - {% internal_link "subscriptions", "/subscriptions/overview" %}, - {% internal_link "streaming responses", "/defer/overview" %}, - and {% internal_link "multiplexing", "/queries/multiplex" %}. -

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

Harden Your API

-

- Confidently deploy GraphQL with GraphQL-Ruby: -

    -
  • {% internal_link "Testing helpers", "/testing/overview" %} to validate your system
  • -
  • {% internal_link "Authorization", "/authorization/overview" %} integrates with your app's permission system
  • -
  • {% internal_link "GraphQL::Dataloader", "/dataloader/overview" %} optimizes access to data sources
  • -
  • {% internal_link "Complexity limits", "/queries/complexity_and_depth" %}, {% internal_link "timeouts", "/queries/timeout" %}, and {% internal_link "rate limits", "/limiters/overview" %} to protect your server resources
  • -
  • {% internal_link "Tracing", "/queries/tracing" %} for integration with your APM or custom usage
  • -
  • {% internal_link "API versioning", "/changesets/overview" %} to roll out changes while preserving client experience
  • -
  • {% internal_link "Persisted queries", "/operation_store/overview" %} to guarantee approved API usage
  • -
  • {% internal_link "Caching", "/object_cache/overview" %} to serve repeated data requests
  • -
-

-
-
-
-
-

Integrate with Client Libraries

-

- {% internal_link "graphql-ruby-client", "/javascript_client/overview" %} provides integration with - {% internal_link "Apollo Client", "/javascript_client/apollo_subscriptions" %}, - {% internal_link "Relay", "/javascript_client/relay_subscriptions" %}, - {% internal_link "GraphiQL", "/javascript_client/graphiql_subscriptions" %}, - {% internal_link "urql", "/javascript_client/urql_subscriptions" %}, or custom JavaScript. -

-
-
-

Going Beyond

-

- Customize your GraphQL API: -

    -
  • {% internal_link "Language tooling", "/language_tools/visitor/ %} for manipulating GraphQL documents
  • -
  • {% internal_link "Type system extensions", "/type_definitions/extensions/ %} for customizing your schema definition
  • -
  • {% internal_link "Query analysis", "/queries/ast_analysis" %} for ahead-of-time query inspection
  • -
-

-
-
-
- -

- Add GraphQL to your Ruby app. Get Started! -

diff --git a/guides/javascript_client/apollo_subscriptions.md b/guides/javascript_client/apollo_subscriptions.md index 3b09d6f7b70..c9011e3d2ce 100644 --- a/guides/javascript_client/apollo_subscriptions.md +++ b/guides/javascript_client/apollo_subscriptions.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: JavaScript Client -title: Apollo Subscriptions -desc: GraphQL subscriptions with GraphQL-Ruby and Apollo Client -index: 2 ---- +# Apollo Subscriptions GraphQL-Ruby's JavaScript client includes several kinds of support for Apollo Client: @@ -63,7 +55,7 @@ const client = new ApolloClient({ 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. -If you're using {% internal_link "compressed payloads", "/subscriptions/pusher_implementation#payload-compression" %}, configure a `decompress:` function, too: +If you're using [compressed payloads](/subscriptions/pusher_implementation#payload-compression), configure a `decompress:` function, too: ```javascript // Add `pako` to the project for gunzipping @@ -124,7 +116,7 @@ This link will check responses for the `X-Subscription-ID` header, and if it's p 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" }} +![Ably Subscription Key Privileges](/javascript_client/ably_key.png) ## Apollo Link -- ActionCable @@ -171,11 +163,11 @@ Note that for Rails 5, the ActionCable client package is `actioncable`, not `@ra ## 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" %}. +`graphql-ruby-client` includes support for Apollo 1 client subscriptions over [Pusher](/subscriptions/pusher_implementation) or [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. +See the [Subscriptions guide](/subscriptions/overview) for information about server-side setup. ### Apollo 1 -- Pusher @@ -195,7 +187,7 @@ var OperationStoreClient = require("./OperationStoreClient") RailsNetworkInterface.use([OperationStoreClient.apolloMiddleware]) ``` -If you're using {% internal_link "compressed payloads", "/subscriptions/pusher_implementation#payload-compression" %}, configure a `decompress:` function, too: +If you're using [compressed payloads](/subscriptions/pusher_implementation#payload-compression), configure a `decompress:` function, too: ```javascript // Add `pako` to the project for gunzipping diff --git a/guides/javascript_client/graphiql_subscriptions.md b/guides/javascript_client/graphiql_subscriptions.md index 6b1279d61e5..1ab6515e870 100644 --- a/guides/javascript_client/graphiql_subscriptions.md +++ b/guides/javascript_client/graphiql_subscriptions.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: JavaScript Client -title: GraphiQL Subscriptions -desc: Testing GraphQL subscriptions in the GraphiQL IDE -index: 5 ---- +# GraphiQL Subscriptions After setting up your server, you can integrate subscriptions into [GraphiQL](https://github.com/graphql/graphiql/tree/main/packages/graphiql#readme), the in-browser GraphQL IDE. @@ -37,7 +29,7 @@ After that, you should be able to load the page in your app and see the GraphiQL ## Ably -To integrate {% internal_link "Ably subscriptions", "subscriptions/ably_implementation" %}, use `createAblyFetcher`, for example: +To integrate [Ably subscriptions](/subscriptions/ably_implementation), use `createAblyFetcher`, for example: ```js import Ably from "ably" @@ -57,7 +49,7 @@ Under the hood, it will use `window.fetch` to send GraphQL operations to the ser ## Pusher -To integrate {% internal_link "Pusher subscriptions", "subscriptions/pusher_implementation" %}, use `createPusherFetcher`, for example: +To integrate [Pusher subscriptions](/subscriptions/pusher_implementation), use `createPusherFetcher`, for example: ```js import Pusher from "pusher-js" @@ -76,7 +68,7 @@ Under the hood, it will use `window.fetch` to send GraphQL operations to the ser ## ActionCable -To integrate {% internal_link "ActionCable subscriptions", "subscriptions/action_cable_implementation" %}, use `createActionCableFetcher`, for example: +To integrate [ActionCable subscriptions](/subscriptions/action_cable_implementation), use `createActionCableFetcher`, for example: ```js import { createConsumer } from "@rails/actioncable" diff --git a/guides/javascript_client/overview.md b/guides/javascript_client/overview.md index 6e9b4f41d81..cad18f2c3af 100644 --- a/guides/javascript_client/overview.md +++ b/guides/javascript_client/overview.md @@ -1,12 +1,4 @@ ---- -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 ---- +# Overview There is a JavaScript client for GraphQL-Ruby, `graphql-ruby-client`. @@ -22,9 +14,9 @@ The source code is [in the graphql-ruby repository](https://github.com/rmosolgo/ 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 +- [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" %} - - {% internal_link "urql integration", "/javascript_client/urql_subscriptions" %} - - {% internal_link "GraphiQL integration", "/javascript_client/graphiql_subscriptions" %} + - [Apollo integration](/javascript_client/apollo_subscriptions) + - [Relay integration](/javascript_client/relay_subscriptions) + - [urql integration](/javascript_client/urql_subscriptions) + - [GraphiQL integration](/javascript_client/graphiql_subscriptions) diff --git a/guides/javascript_client/relay_subscriptions.md b/guides/javascript_client/relay_subscriptions.md index c641ac74c27..c9a5ea256c8 100644 --- a/guides/javascript_client/relay_subscriptions.md +++ b/guides/javascript_client/relay_subscriptions.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: JavaScript Client -title: Relay Subscriptions -desc: GraphQL subscriptions with GraphQL-Ruby and Relay Modern -index: 3 ---- +# Relay Subscriptions `graphql-ruby-client` includes three kinds of support for subscriptions with Relay Modern: @@ -19,11 +11,11 @@ To use it, require `graphql-ruby-client/subscriptions/createRelaySubscriptionHan __Note:__ For Relay <11, use `import { createLegacyRelaySubscriptionHandler } from "graphql-ruby-client/subscriptions/createRelaySubscriptionHandler"` instead; the signature changed in Relay 11. -See the {% internal_link "Subscriptions guide", "/subscriptions/overview" %} for information about server-side setup. +See the [Subscriptions guide](/subscriptions/overview) for information about server-side setup. ## Pusher -Subscriptions with {% internal_link "Pusher", "/subscriptions/pusher_implementation" %} require two things: +Subscriptions with [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 @@ -57,7 +49,7 @@ var network = Network.create(fetchQuery, subscriptionHandler) ### Compressed Payloads -If you're using {% internal_link "compressed payloads", "/subscriptions/pusher_implementation#payload-compression" %}, configure a `decompress:` function, too: +If you're using [compressed payloads](/subscriptions/pusher_implementation#payload-compression), configure a `decompress:` function, too: ```javascript // Add `pako` to the project for gunzipping @@ -79,7 +71,7 @@ var subscriptionHandler = createRelaySubscriptionHandler({ ## Ably -Subscriptions with {% internal_link "Ably", "/subscriptions/ably_implementation" %} require two things: +Subscriptions with [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 @@ -113,7 +105,7 @@ var network = Network.create(fetchQuery, subscriptionHandler) ## ActionCable -With this configuration, `subscription` queries will be routed to {% internal_link "ActionCable", "/subscriptions/action_cable_implementation" %}. +With this configuration, `subscription` queries will be routed to [ActionCable](/subscriptions/action_cable_implementation). For example: @@ -135,7 +127,7 @@ var network = Network.create(fetchQuery, subscriptionHandler) ## With Relay Persisted Queries -If you're using Relay's built-in [persisted query support](https://relay.dev/docs/guides/persisted-queries/), you can pass `clientName:` to the handler in order to build IDs that work with the {% internal_link "OperationStore", "/operation_store/overview.html" %}. For example: +If you're using Relay's built-in [persisted query support](https://relay.dev/docs/guides/persisted-queries/), you can pass `clientName:` to the handler in order to build IDs that work with the [OperationStore](/operation_store/overview.html). For example: ```js var subscriptionHandler = createRelaySubscriptionHandler({ diff --git a/guides/javascript_client/sync.md b/guides/javascript_client/sync.md index 4d818b017ae..6655c687ac5 100644 --- a/guides/javascript_client/sync.md +++ b/guides/javascript_client/sync.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: JavaScript Client -title: OperationStore Sync -desc: Javascript tooling for persisted queries with GraphQL-Ruby -index: 1 ---- +# OperationStore Sync JavaScript support for GraphQL projects using [graphql-pro](https://graphql.pro)'s `OperationStore` for persisted queries. @@ -21,7 +13,7 @@ JavaScript support for GraphQL projects using [graphql-pro](https://graphql.pro) - [Plain JS support](#use-with-plain-javascript) - [Authorization](#authorization) -See the {% internal_link "OperationStore guide", "/operation_store/overview" %} for server-side setup. +See the [OperationStore guide](/operation_store/overview) for server-side setup. ## `sync` utility @@ -42,19 +34,19 @@ Generating client module in app/javascript/graphql/OperationStoreClient.js... option | description --------|---------- -`--url` | {% internal_link "Sync API", "/operation_store/getting_started.html#add-routes" %} url +`--url` | [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-codegen-json-output` | Path to a `.json` file from `apollo client:codegen ... --target json` `--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" %}) +`--client` | Client ID ([created on server](/operation_store/client_workflow)) +`--secret` | Client Secret ([created on server](/operation_store/client_workflow)) `--outfile` | Destination for generated code `--outfile-type` | What kind of code to generate (`js` or `json`) `--header={key}:{value}` | Add a header to the outgoing HTTP request (may be repeated) `--add-typename` | Add `__typename` to all selection sets (for use with Apollo Client) `--verbose` | Output some debug information -`--changeset-version` | Set a {% internal_link "Changeset Version", "/changesets/installation#controller-setup" %} when syncing these queries. (`context[:changeset_version]` will also be required at runtime, when running these stored operations.) +`--changeset-version` | Set a [Changeset Version](/changesets/installation#controller-setup) when syncing these queries. (`context[:changeset_version]` will also be required at runtime, when running these stored operations.) `--dump-payload` | A file to write the HTTP Post payload into, or if no filename is passed, then the payload will be written to stdout. You can see these and a few others with `graphql-ruby-client sync --help`. @@ -267,7 +259,7 @@ You may also have to __update your app__ to send an identifier, so that the serv ## Use with Apollo Persisted Queries -Apollo client has a [Persisted Queries Link](https://www.apollographql.com/docs/react/api/link/persisted-queries/). You can use that link with GraphQL-Pro's {% internal_link "OperationStore", "/operation_store/overview" %}. First, create a manifest with [`generate-persisted-query-manifest`](https://www.apollographql.com/docs/react/api/link/persisted-queries/#1-generate-operation-manifests), then, pass the path to that file to `sync`: +Apollo client has a [Persisted Queries Link](https://www.apollographql.com/docs/react/api/link/persisted-queries/). You can use that link with GraphQL-Pro's [OperationStore](/operation_store/overview). First, create a manifest with [`generate-persisted-query-manifest`](https://www.apollographql.com/docs/react/api/link/persisted-queries/#1-generate-operation-manifests), then, pass the path to that file to `sync`: ```sh $ graphql-ruby-client sync --apollo-persisted-query-manifest=path/to/manifest.json ... @@ -316,7 +308,7 @@ $.post("/graphql", { ## Authorization -`OperationStore` uses HMAC-SHA256 to {% internal_link "authenticate requests" , "/operation_store/access_control" %}. +`OperationStore` uses HMAC-SHA256 to [authenticate requests](/operation_store/access_control). Pass the key to `graphql-ruby-client sync` as `--secret` to authenticate it: diff --git a/guides/javascript_client/urql_subscriptions.md b/guides/javascript_client/urql_subscriptions.md index 25983705666..53c695f4bd0 100644 --- a/guides/javascript_client/urql_subscriptions.md +++ b/guides/javascript_client/urql_subscriptions.md @@ -1,14 +1,6 @@ ---- -layout: guide -doc_stub: false -search: true -section: JavaScript Client -title: urql Subscriptions -desc: GraphQL subscriptions with GraphQL-Ruby and urql -index: 4 ---- - -GraphQL-Ruby currently supports using `urql` with the {% internal_link "ActionCable", "/subscriptions/action_cable_implementation" %} and {% internal_link "Pusher implementation", "/subscriptions/pusher_implementation" %}. +# urql Subscriptions + +GraphQL-Ruby currently supports using `urql` with the [ActionCable](/subscriptions/action_cable_implementation) and [Pusher implementation](/subscriptions/pusher_implementation). ## Pusher @@ -51,4 +43,4 @@ const client = new Client({ }); ``` -Want to use `urql` with another subscription backend? Please {% open_an_issue "Using urql with ..." %}. +Want to use `urql` with another subscription backend? Please [open an issue](https://github.com/rmosolgo/graphql-ruby/issues/new?title=Using+urql+with+...&body=). diff --git a/guides/js/search.js b/guides/js/search.js deleted file mode 100644 index 7a9cedcd982..00000000000 --- a/guides/js/search.js +++ /dev/null @@ -1,103 +0,0 @@ -var 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() - } - } -}) diff --git a/guides/language_tools/c_parser.md b/guides/language_tools/c_parser.md index 820f3e59e64..f6f766af53f 100644 --- a/guides/language_tools/c_parser.md +++ b/guides/language_tools/c_parser.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Language Tools -title: C-based Parser -desc: The GraphQL::CParser gem is a drop-in replacement for the built-in parser -index: 1 ---- +# C-based Parser GraphQL-Ruby includes a plain-Ruby parser, but a faster parser is available as a C extension. To use it, add the [`graphql-c_parser` gem](https://rubygems.org/gems/graphql-c_parser) to your project, for example: @@ -14,7 +6,7 @@ GraphQL-Ruby includes a plain-Ruby parser, but a faster parser is available as a bundle add graphql-c_parser ``` -When `graphql-c_parser` is `require`d by your app, the C-based parser is installed as the default parser (as {{ "GraphQL.default_parser" | api_doc }}). Bundler requires the library automatically, but you can also require it manually: +When `graphql-c_parser` is `require`d by your app, the C-based parser is installed as the default parser (as [GraphQL.default_parser](rdoc-ref:GraphQL.default_parser)). Bundler requires the library automatically, but you can also require it manually: ```ruby require "graphql/c_parser" diff --git a/guides/language_tools/visitor.md b/guides/language_tools/visitor.md index f6cb3ce6bda..e2e67266ec2 100644 --- a/guides/language_tools/visitor.md +++ b/guides/language_tools/visitor.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Language Tools -title: AST Visitor -desc: Analyze and modify parsed GraphQL code -index: 0 ---- +# AST Visitor GraphQL code is usually contained in a string, for example: @@ -22,14 +14,14 @@ You can perform programmatic analysis and modifications to GraphQL code using a ## Parse -{{ "GraphQL.parse" | api_doc }} turns a string into a GraphQL document: +[GraphQL.parse](rdoc-ref:GraphQL.parse) 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. +Also, [GraphQL.parse_file](rdoc-ref:GraphQL.parse_file) parses the contents of the named file and includes a `filename` in the parsed document. #### AST Nodes @@ -54,14 +46,14 @@ Above, `field_node` is unmodified, but `modified_node` reflects the new name and ## 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. +To inspect or modify a parsed document, extend [GraphQL::Language::Visitor](rdoc-ref:GraphQL::Language::Visitor) 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` +- [GraphQL::Language::Nodes::Field](rdoc-ref:GraphQL::Language::Nodes::Field)s are routed to `#on_field` +- [GraphQL::Language::Nodes::Argument](rdoc-ref:GraphQL::Language::Nodes::Argument)s are routed to `#on_argument` -See the {{ "GraphQL::Language::Visitor" | api_doc }} API docs for a full list of methods. +See the [GraphQL::Language::Visitor](rdoc-ref:GraphQL::Language::Visitor) API docs for a full list of methods. Each method is called with `(node, parent)`, where: @@ -129,15 +121,15 @@ end This will add `emailAddress` the fields selection on `node`. -(These `.add_*` helpers are wrappers around {{ "GraphQL::Language::Nodes::AbstractNode#merge" | api_doc }}.) +(These `.add_*` helpers are wrappers around [GraphQL::Language::Nodes::AbstractNode#merge](rdoc-ref:GraphQL::Language::Nodes::AbstractNode#merge).) ## 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: +The easiest way to turn an AST back into a string of GraphQL is [GraphQL::Language::Nodes::AbstractNode#to_query_string](rdoc-ref:GraphQL::Language::Nodes::AbstractNode#to_query_string), 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. +You can also create a subclass of [GraphQL::Language::Printer](rdoc-ref:GraphQL::Language::Printer) to customize how nodes are printed. diff --git a/guides/limiters/active_operations.md b/guides/limiters/active_operations.md index c104523478b..9515e521692 100644 --- a/guides/limiters/active_operations.md +++ b/guides/limiters/active_operations.md @@ -1,15 +1,6 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Rate Limiters -title: Active Operation Limiter -desc: Limit the number of concurrent GraphQL operations -index: 2 ---- - -`GraphQL::Enterprise::ActiveOperationLimiter` prevents clients from running too many GraphQL operations at the same time. It uses {% internal_link "Redis", "limiters/redis" %} to track currently-running operations. +# Active Operation Limiter + +`GraphQL::Enterprise::ActiveOperationLimiter` prevents clients from running too many GraphQL operations at the same time. It uses [Redis](/limiters/redis) to track currently-running operations. ## Why? @@ -39,7 +30,7 @@ end It also accepts a `stale_request_seconds:` option. The limiter uses that value to clean up request data in case of a crash or other unexpected scenario. -Before requests will actually be halted, {% internal_link "soft mode", "/limiters/deployment#soft-limits" %} must be disabled. +Before requests will actually be halted, [soft mode](/limiters/deployment#soft-limits) must be disabled. #### Query Setup diff --git a/guides/limiters/deployment.md b/guides/limiters/deployment.md index 0b2d21c7be7..8849ca7f4e3 100644 --- a/guides/limiters/deployment.md +++ b/guides/limiters/deployment.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Rate Limiters -title: Deploying Rate Limiters -desc: Tips for releasing limiters smoothly -index: 4 ---- +# Deploying Rate Limiters Here are a few options for deploying GraphQL-Enterprise's rate limiters: @@ -19,15 +10,15 @@ Here are a few options for deploying GraphQL-Enterprise's rate limiters: ## Dashboard -Once installed, your {% internal_link "GraphQL-Pro dashboard", "/pro/dashboard" %} will include a simple metrics view: +Once installed, your [GraphQL-Pro dashboard](/pro/dashboard) will include a simple metrics view: -{{ "/limiters/active_operation_limiter_dashboard.png" | link_to_img:"GraphQL Active Operation Limiter Dashboard" }} +![GraphQL Active Operation Limiter Dashboard](/limiters/active_operation_limiter_dashboard.png) To disable dashboard charts, add `use(... dashboard_charts: false)` to your configuration. Also, the dashboard includes a link to enable or disable "soft mode": -{{ "/limiters/soft_button.png" | link_to_img:"GraphQL Rate Limiter Soft Mode Button" }} +![GraphQL Rate Limiter Soft Mode Button](/limiters/soft_button.png) When "soft mode" is enabled, limited requests are _not_ actually halted (although they are _counted_). When "soft mode" is disabled, any over-limit requests are halted. @@ -56,7 +47,7 @@ MySchema.enterprise_runtime_limiter.set_soft_limit(false) ## Subscriptions -If you're using {% internal_link "PusherSubscriptions", "/subscriptions/pusher_implementation" %} or {% internal_link "AblySubscriptions", "/subscriptions/ably_implementation" %}, then you'll need to accomodate subscriptions that were created _before_ you deployed the rate limiter. Those subscriptions are already stored in Redis and their contexts _don't_ include the required `limiter_key:` value. +If you're using [PusherSubscriptions](/subscriptions/pusher_implementation) or [AblySubscriptions](/subscriptions/ably_implementation), then you'll need to accomodate subscriptions that were created _before_ you deployed the rate limiter. Those subscriptions are already stored in Redis and their contexts _don't_ include the required `limiter_key:` value. To address this, you can customize the limiter(s) you're using to provide a default value in this case. For example: diff --git a/guides/limiters/overview.md b/guides/limiters/overview.md index 260560ed63f..f1739fdf8fe 100644 --- a/guides/limiters/overview.md +++ b/guides/limiters/overview.md @@ -1,14 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Rate Limiters -title: Rate Limiters for GraphQL -desc: Manage access to your GraphQL API -index: 0 ---- - +# Rate Limiters for GraphQL `GraphQL::Enterprise` includes rate limiters built especially for GraphQL. @@ -21,6 +11,6 @@ There's some overlap in these limiters; both of them constrain the amount of _ti To get started, read on: -- {% internal_link "Configure Redis", "limiters/redis" %} for the limiters' backend -- {% internal_link "Active Operation Limiter", "limiters/active_operations" %} -- {% internal_link "Runtime Limiter", "limiters/runtime" %} +- [Configure Redis](/limiters/redis) for the limiters' backend +- [Active Operation Limiter](/limiters/active_operations) +- [Runtime Limiter](/limiters/runtime) diff --git a/guides/limiters/redis.md b/guides/limiters/redis.md index 43b8df00f26..2af67e479c7 100644 --- a/guides/limiters/redis.md +++ b/guides/limiters/redis.md @@ -1,15 +1,6 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Rate Limiters -title: Configuring Redis -desc: Preparing the rate limiter backend -index: 1 ---- - -Rate limiting requires a persistent Redis instance, just like [Sidekiq](https://github.com/mperham/sidekiq/wiki/Using-Redis) or the {% internal_link "Operation Store", "/operation_store/redis_backend" %}. Set `maxmemory-policy noeviction` in `redis.conf` to ensure that Redis doesn't silently drop keys when it reaches its memory limit. +# Configuring Redis + +Rate limiting requires a persistent Redis instance, just like [Sidekiq](https://github.com/mperham/sidekiq/wiki/Using-Redis) or the [Operation Store](/operation_store/redis_backend). Set `maxmemory-policy noeviction` in `redis.conf` to ensure that Redis doesn't silently drop keys when it reaches its memory limit. ## Memory Usage diff --git a/guides/limiters/runtime.md b/guides/limiters/runtime.md index 0f2084ccf74..f42a9d79097 100644 --- a/guides/limiters/runtime.md +++ b/guides/limiters/runtime.md @@ -1,19 +1,10 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Rate Limiters -title: Runtime Limiter -desc: Limit the total runtime of a client's GraphQL Operations -index: 3 ---- - -`GraphQL::Enterprise::RuntimeLimiter` applies an upper bound to processing time consumed by a single client. It uses {% internal_link "Redis", "limiters/redis" %} track time with a [token bucket](https://en.wikipedia.org/wiki/Token_bucket) algorithm. +# Runtime Limiter + +`GraphQL::Enterprise::RuntimeLimiter` applies an upper bound to processing time consumed by a single client. It uses [Redis](/limiters/redis) track time with a [token bucket](https://en.wikipedia.org/wiki/Token_bucket) algorithm. ## Why? -This limiter prevents a single client from consuming too much processing time, regardless of whether it comes a burst of short-lived queries (which the {% internal_link "Active Operation Limiter", "/limiters/active_operations" %} can prevent) or a small number of long-running queries. Unlike request counters or complexity calculations, the runtime limiter pays no attention to the structure of the incoming request. Instead, it simply measures the time spent on the request _as a whole_ and halts queries when a client consumes more than the limit. +This limiter prevents a single client from consuming too much processing time, regardless of whether it comes a burst of short-lived queries (which the [Active Operation Limiter](/limiters/active_operations) can prevent) or a small number of long-running queries. Unlike request counters or complexity calculations, the runtime limiter pays no attention to the structure of the incoming request. Instead, it simply measures the time spent on the request _as a whole_ and halts queries when a client consumes more than the limit. ## Setup @@ -39,7 +30,7 @@ end It also accepts a `window_ms:` option, which is the duration over which `limit_ms:` is added to a client's bucket. It defaults to `60_000` (one minute). -Before requests will actually be halted, {% internal_link "soft mode", "/limiters/deployment#soft-limits" %} must be disabled. +Before requests will actually be halted, [soft mode](/limiters/deployment#soft-limits) must be disabled. ### Query Setup @@ -114,4 +105,4 @@ MyMetrics.increment("graphql.runtime_limiter", tags: result.context[:runtime_lim The limiter will not _interrupt_ a long-running field. Instead, it stops executing new fields after a client exceeds its allowed processing time. This is because interrupting arbitrary code may have unintended consequences for I/O operations, see ["Timeout: Ruby's most dangerous API"](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/). -Also, the limiter only checks remaining time at the _start_ of a query and it only decreases the remaining time at the _end_ of a query. This means that simulaneous queries may consume the remainder at the same time. Use the {% internal_link "Active Operation Limiter", "/limiters/active_operations" %} to limit behavior in this regard. This implementation is basically a trade-off: more granular updates would require more communication with Redis which would add overhead to each request. +Also, the limiter only checks remaining time at the _start_ of a query and it only decreases the remaining time at the _end_ of a query. This means that simulaneous queries may consume the remainder at the same time. Use the [Active Operation Limiter](/limiters/active_operations) to limit behavior in this regard. This implementation is basically a trade-off: more granular updates would require more communication with Redis which would add overhead to each request. diff --git a/guides/mutations/mutation_authorization.md b/guides/mutations/mutation_authorization.md index f5b08c0f9f5..fd7d46cce53 100644 --- a/guides/mutations/mutation_authorization.md +++ b/guides/mutations/mutation_authorization.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Mutations -title: Mutation authorization -desc: Checking permissions for mutations -index: 3 ---- +# Mutation authorization Before running a mutation, you probably want to do a few things: @@ -57,7 +49,7 @@ 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.html#errors-as-data" %}: +Additionally, `#ready?` may return `false, { ... }` to return [errors as data](/mutations/mutation_errors.html#errors-as-data): ```ruby def ready? @@ -93,9 +85,9 @@ 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" %}) +- Add a prepare hook to fetch an object with the given `ID` (using [Schema.object_from_id](rdoc-ref:GraphQL::Schema.object_from_id)) +- Check that the fetched object's type matches the `loads:` type (using [Schema.resolve_type](rdoc-ref:GraphQL::Schema.resolve_type)) +- Run the fetched object through its type's `.authorized?` hook (see [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. @@ -133,7 +125,7 @@ When `#authorized?` returns `false` (or something falsey), the mutation will be #### Adding errors -To add errors as data (as described in {% internal_link "Mutation errors", "/mutations/mutation_errors.html#errors-as-data" %}), return a value _along with_ `false`, for example: +To add errors as data (as described in [Mutation errors](/mutations/mutation_errors.html#errors-as-data)), return a value _along with_ `false`, for example: ```ruby def authorized?(employee:) diff --git a/guides/mutations/mutation_classes.md b/guides/mutations/mutation_classes.md index 513ce24c968..064910402f8 100644 --- a/guides/mutations/mutation_classes.md +++ b/guides/mutations/mutation_classes.md @@ -1,15 +1,4 @@ ---- -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/ - - /relay/mutations/ ---- +# Mutation Classes GraphQL _mutations_ are special fields: instead of reading data or performing calculations, they may _modify_ the application state. For example, mutation fields may: @@ -28,14 +17,14 @@ Like all GraphQL fields, mutation 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. +- [GraphQL::Schema::Mutation](rdoc-ref:GraphQL::Schema::Mutation), a bare-bones base class +- [GraphQL::Schema::RelayClassicMutation](rdoc-ref:GraphQL::Schema::RelayClassicMutation), 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. +Besides those, you can also use the plain [field API](/type_definitions/objects#fields) to write mutation fields. ## 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: +If you used the [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 @@ -82,7 +71,7 @@ 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.) +(See [Mutation Errors](/mutations/mutation_errors) for more information about returning errors.) Also, you can configure `null(false)` in your mutation class to make the generated payload class non-null. @@ -118,7 +107,7 @@ class Mutations::AddStar < Mutations::BaseMutation 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" %} with the provided `post_id`. +By specifying that the `post_id` argument loads a `Types::Post` object type, a `Post` object will be loaded via [`Schema.object_from_id`](/schema/definition.html#object-identification) 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. @@ -164,11 +153,11 @@ In the above examples, `loads:` is provided a concrete type, but it also support ### Resolving the type of loaded objects -When `loads:` gets an object from {{ "Schema.object_from_id" | api_doc }}, it passes that object to {{ "Schema.resolve_type" | api_doc }} to confirm that it resolves to the same type originally configured with `loads:`. +When `loads:` gets an object from [Schema.object_from_id](rdoc-ref:GraphQL::Schema.object_from_id), it passes that object to [Schema.resolve_type](rdoc-ref:GraphQL::Schema.resolve_type) to confirm that it resolves to the same type originally configured with `loads:`. ### Handling failed loads -If `loads:` fails to find an object or if the loaded object isn't resolved to the specified `loads:` type (using {{ "Schema.resolve_type" | api_doc }}), a {{ "GraphQL::LoadApplicationObjectFailedError" | api_doc }} is raised and returned to the client. +If `loads:` fails to find an object or if the loaded object isn't resolved to the specified `loads:` type (using [Schema.resolve_type](rdoc-ref:GraphQL::Schema.resolve_type)), a [GraphQL::LoadApplicationObjectFailedError](rdoc-ref:GraphQL::LoadApplicationObjectFailedError) is raised and returned to the client. You can customize this behavior by implementing `def load_application_object_failed` in your mutation class, for example: @@ -182,7 +171,7 @@ Or, if `load_application_object_failed` returns a new object, that object will b ### Handling unauthorized loaded objects -When an object is _loaded_ but fails its {% internal_link "`.authorized?` check", "/authorization/authorization#object-authorization" %}, a {{ "GraphQL::UnauthorizedError" | api_doc }} is raised. By default, it's passed to {{ "Schema.unauthorized_object" | api_doc }} (see {% internal_link "Handling Unauthorized Objects", "/authorization/authorization.html#handling-unauthorized-objects" %}). You can customize this behavior by implementing `def unauthorized_object(err)` in your mutation, for example: +When an object is _loaded_ but fails its [`.authorized?` check](/authorization/authorization#object-authorization), a [GraphQL::UnauthorizedError](rdoc-ref:GraphQL::UnauthorizedError) is raised. By default, it's passed to [Schema.unauthorized_object](rdoc-ref:GraphQL::Schema.unauthorized_object) (see [Handling Unauthorized Objects](/authorization/authorization.html#handling-unauthorized-objects)). You can customize this behavior by implementing `def unauthorized_object(err)` in your mutation, for example: ```ruby def unauthorized_object(error) diff --git a/guides/mutations/mutation_errors.md b/guides/mutations/mutation_errors.md index 6d1f74cd363..8e041c60d8c 100644 --- a/guides/mutations/mutation_errors.md +++ b/guides/mutations/mutation_errors.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Mutations -title: Mutation errors -desc: Tips for handling and returning errors from mutations -index: 2 ---- +# Mutation errors How can you handle errors inside mutations? Let's explore a couple of options. diff --git a/guides/mutations/mutation_root.md b/guides/mutations/mutation_root.md index 08141ea8b32..4b9b7de4e8b 100644 --- a/guides/mutations/mutation_root.md +++ b/guides/mutations/mutation_root.md @@ -1,12 +1,4 @@ ---- -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 ---- +# Mutation Root GraphQL mutations all begin with the `mutation` keyword: @@ -41,4 +33,4 @@ 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. +See [Mutation Classes](/mutations/mutation_classes) for some helpers to define mutation fields. diff --git a/guides/object_cache/caching.md b/guides/object_cache/caching.md index 1d39b94b14e..66edb154e50 100644 --- a/guides/object_cache/caching.md +++ b/guides/object_cache/caching.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Object Cache -title: Caching Results -desc: Configuration options for caching objects and fields -index: 2 ---- +# Caching Results `GraphQL::Enterprise::ObjectCache` supports several different caching configurations for objects and fields. To get started, include the extension in your base object class and base field class and use `cacheable(...)` to set up the default cache behavior: @@ -57,7 +48,7 @@ Only _queries_ are cached. `ObjectCache` skips mutations and subscriptions altog ## `public:` -`cacheable(public: false)` means that a type or field may be _cached_, but {% internal_link "`Schema.private_context_fingerprint_for(ctx)`", "/object_cache/schema_setup#context-fingerprint" %} should be included in its cache key. In practice, this means that each client can have its own cached responses. Any query that contains a `cacheable(public: false)` type or field will use a private cache key. +`cacheable(public: false)` means that a type or field may be _cached_, but [`Schema.private_context_fingerprint_for(ctx)`](/object_cache/schema_setup#context-fingerprint) should be included in its cache key. In practice, this means that each client can have its own cached responses. Any query that contains a `cacheable(public: false)` type or field will use a private cache key. `cacheable(public: true)` means that cached values from this type or field may be shared by _all_ clients. Use this for public-facing data which is the same for all viewers. Queries that include _only_ `public: true` types and fields will not include `Schema.private_context_fingerprint_for(ctx)` in their cache keys. That way their responses will be shared by all clients who request them. @@ -126,7 +117,7 @@ class Query < GraphQL::Schema::Object end ``` -If you're using {{ "GraphQL::Schema::Resolver" | api_doc }}, you'd call `.items_for` slightly differently: +If you're using [GraphQL::Schema::Resolver](rdoc-ref:GraphQL::Schema::Resolver), you'd call `.items_for` slightly differently: ```ruby def resolve(division: nil) @@ -174,7 +165,7 @@ By default, connection-related objects (like `*Connection` and `*Edge` types) "i ## Caching Introspection -By default, introspection fields are considered _public_ for all queries. This means that they are considered cacheable and their results will be reused for any clients who request them. When {% internal_link "adding the ObjectCache to your schema", "/object_cache/schema_setup#add-the-cache", %}, you can provide some options to customize this behavior: +By default, introspection fields are considered _public_ for all queries. This means that they are considered cacheable and their results will be reused for any clients who request them. When [adding the ObjectCache to your schema](/object_cache/schema_setup#add-the-cache), you can provide some options to customize this behavior: - `cache_introspection: { public: false, ... }` to use [`public: false`](#public) for all introspection fields. Use this if you hide schema members for some clients. - `cache_introspection: false` to completely disable caching on introspection fields. diff --git a/guides/object_cache/memcached.md b/guides/object_cache/memcached.md index 7ab0b9af266..fbf5144c38a 100644 --- a/guides/object_cache/memcached.md +++ b/guides/object_cache/memcached.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Object Cache -title: Dalli Configuration -desc: Setting up the Memcached backend -index: 3 ---- +# Dalli Configuration `GraphQL::Enterprise::ObjectCache` can also run with a Memcached backend via the [Dalli](https://github.com/petergoldstein/dalli) client gem. diff --git a/guides/object_cache/overview.md b/guides/object_cache/overview.md index d04d93e648f..3bb84ea9010 100644 --- a/guides/object_cache/overview.md +++ b/guides/object_cache/overview.md @@ -1,15 +1,6 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Object Cache -title: GraphQL ObjectCache -desc: A server-side cache for GraphQL-Ruby -index: 0 ---- - -`GraphQL::Enterprise::ObjectCache` is an application-level cache for GraphQL-Ruby servers. It works by storing a {% internal_link "_cache fingerprint_ for each object", "/object_cache/schema_setup#object-fingerprint" %} in a query, then serving a cached response as long as those fingerprints don't change. The cache can also be customized with {% internal_link "TTLs", "/object_cache/caching#ttl" %}. +# GraphQL ObjectCache + +`GraphQL::Enterprise::ObjectCache` is an application-level cache for GraphQL-Ruby servers. It works by storing a [_cache fingerprint_ for each object](/object_cache/schema_setup#object-fingerprint) in a query, then serving a cached response as long as those fingerprints don't change. The cache can also be customized with [TTLs](/object_cache/caching#ttl). ## Why? @@ -18,20 +9,20 @@ index: 0 Usually, a GraphQL query alternates between data fetching and calling application logic: -{{ "/object_cache/query-without-cache.png" | link_to_img:"GraphQL-Ruby profile, without caching" }} +![GraphQL-Ruby profile, without caching](/object_cache/query-without-cache.png) But with `ObjectCache`, it checks the cache first, returning a cached response if possible: -{{ "/object_cache/query-with-cache.png" | link_to_img:"GraphQL-Ruby profile, with ObjectCache" }} +![GraphQL-Ruby profile, with ObjectCache](/object_cache/query-with-cache.png) This reduces latency for clients and reduces the load on your database and application server. ## How -Before running a query, `ObjectCache` creates a fingerprint for the query using {{ "GraphQL::Query#fingerprint" | api_doc }} and {% internal_link "`Schema.context_fingerprint_for(ctx)`", "/object_cache/schema_setup#context-fingerprint" %}. Then, it checks the backend for a cached response which matches the fingerprint. +Before running a query, `ObjectCache` creates a fingerprint for the query using [GraphQL::Query#fingerprint](rdoc-ref:GraphQL::Query#fingerprint) and [`Schema.context_fingerprint_for(ctx)`](/object_cache/schema_setup#context-fingerprint). Then, it checks the backend for a cached response which matches the fingerprint. -If a match is found, the `ObjectCache` fetches the objects previously visited by this query. Then, it compares the current fingerprint of each object ot the one in the cache and checks `.authorized?` for that object. If the fingerprints all match and all objects pass authorization checks, then the cached response returned. (Authorization checks can be {% internal_link "disabled", "/object_cache/schema_setup#disabling-reauthorization" %}.) +If a match is found, the `ObjectCache` fetches the objects previously visited by this query. Then, it compares the current fingerprint of each object ot the one in the cache and checks `.authorized?` for that object. If the fingerprints all match and all objects pass authorization checks, then the cached response returned. (Authorization checks can be [disabled](/object_cache/schema_setup#disabling-reauthorization).) If there is no cached response or if the fingerprints don't match, then the incoming query is re-evaluated. While it's executed, `ObjectCache` gathers the IDs and fingerprints of each object it encounters. When the query is done, the result and the new object fingerprints are written to the cache. @@ -39,7 +30,7 @@ If there is no cached response or if the fingerprints don't match, then the inco To get started with the object cache: -- {% internal_link "Prepare the schema", "/object_cache/schema_setup" %} -- Set up a {% internal_link "Redis backend", "/object_cache/redis" %} or {% internal_link "Memcached backend", "/object_cache/memcached" %} -- {% internal_link "Configure types and fields for caching", "/object_cache/caching" %} -- Check out the {% internal_link "runtime considerations", "/object_cache/runtime_considerations" %} +- [Prepare the schema](/object_cache/schema_setup) +- Set up a [Redis backend](/object_cache/redis) or [Memcached backend](/object_cache/memcached) +- [Configure types and fields for caching](/object_cache/caching) +- Check out the [runtime considerations](/object_cache/runtime_considerations) diff --git a/guides/object_cache/redis.md b/guides/object_cache/redis.md index 9d352ed84a1..5baf01c66ae 100644 --- a/guides/object_cache/redis.md +++ b/guides/object_cache/redis.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Object Cache -title: Redis Configuration -desc: Setting up the Redis backend -index: 3 ---- +# Redis Configuration `GraphQL::Enterprise::ObjectCache` requires a Redis connection to store cached responses. Unlike `OperationStore` or rate limiters, this Redis instance should be configured to evict keys as needed. diff --git a/guides/object_cache/runtime_considerations.md b/guides/object_cache/runtime_considerations.md index 4eb959a8ae0..7d3d482e6b3 100644 --- a/guides/object_cache/runtime_considerations.md +++ b/guides/object_cache/runtime_considerations.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Object Cache -title: Runtime Considerations -desc: Settings and observability per-query -index: 4 ---- +# Runtime Considerations With caching configured, here are a few more things to keep in mind while queries are running. @@ -64,4 +55,4 @@ pp result.context[:object_cache] If you need to manually clear the cache for a query, pass `context: { refresh_object_cache: true, ... }`. This will cause the `ObjectCache` to remove the already-cached result (if there was one), reassess the query for cache validity, and return a freshly-executed result. -Usually, this shouldn't be necessary; making sure objects update their {% internal_link "cache fingerprints", "/object_cache/schema_setup.html#object-fingerprint" %} will cause entries to expire when they should be re-executed. See also {% internal_link "Schema fingerprint", "/object_cache/schema_setup.html#schema-fingerprint" %} for expiring _all_ results in the cache. +Usually, this shouldn't be necessary; making sure objects update their [cache fingerprints](/object_cache/schema_setup.html#object-fingerprint) will cause entries to expire when they should be re-executed. See also [Schema fingerprint](/object_cache/schema_setup.html#schema-fingerprint) for expiring _all_ results in the cache. diff --git a/guides/object_cache/schema_setup.md b/guides/object_cache/schema_setup.md index 9d28e46c1b6..8c925d49fc6 100644 --- a/guides/object_cache/schema_setup.md +++ b/guides/object_cache/schema_setup.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -enterprise: true -section: GraphQL Enterprise - Object Cache -title: Schema Setup -desc: Prepare your schema to serve cached responses -index: 1 ---- +# Schema Setup To prepare the schema to serve cached responses, you have to add `GraphQL::Enterprise::ObjectCache` and implement a few hooks. @@ -21,13 +12,13 @@ class MySchema < GraphQL::Schema end ``` -See the {% internal_link "Redis guide", "/object_cache/redis" %} or {% internal_link "Memcached guide", "/object_cache/memcached" %} for details about configuring cache storage. +See the [Redis guide](/object_cache/redis) or [Memcached guide](/object_cache/memcached) for details about configuring cache storage. -Additionally, it accepts some options for customizing how introspection is cached, see {% internal_link "Caching Introspection", "/object_cache/caching#caching-introspection" %} +Additionally, it accepts some options for customizing how introspection is cached, see [Caching Introspection](/object_cache/caching#caching-introspection) ## Context Fingerprint -Additionally, you should implement `def self.private_context_fingerprint_for(context)` to return a string identifying the private scope of the given context. This method will be called whenever a query includes a {% internal_link "`public: false` type or field", "/object_cache/caching#public" %}. For example: +Additionally, you should implement `def self.private_context_fingerprint_for(context)` to return a string identifying the private scope of the given context. This method will be called whenever a query includes a [`public: false` type or field](/object_cache/caching#public). For example: ```ruby class MySchema < GraphQL::Schema @@ -73,7 +64,7 @@ class MySchema < GraphQL::Schema end ``` -The returned strings are used as cache keys in the database -- whenever they change, stale data is left to be {% internal_link "cleaned up by Redis", "/object_cache/redis#memory-management" %}. +The returned strings are used as cache keys in the database -- whenever they change, stale data is left to be [cleaned up by Redis](/object_cache/redis#memory-management). ## Object Identification @@ -83,7 +74,7 @@ The returned strings are used as cache keys in the database -- whenever they cha - `def self.object_from_id(id, context)` which returns the application object for the given globally-unique `id` - `def self.resolve_type(abstract_type, object, context)` which returns a GraphQL object type definition to use for `object` -After your schema is setup, you can {% internal_link "configure caching on your types and fields", "/object_cache/caching", %}. +After your schema is setup, you can [configure caching on your types and fields](/object_cache/caching). ## Schema Fingerprint diff --git a/guides/operation_store/access_control.md b/guides/operation_store/access_control.md index f4a714fea9f..c5ed2ec2cb4 100644 --- a/guides/operation_store/access_control.md +++ b/guides/operation_store/access_control.md @@ -1,19 +1,10 @@ ---- -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 ---- +# Access Control 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. +When you [add a client](/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: @@ -28,4 +19,4 @@ The Authorization header takes the form: "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-client](/javascript_client/sync) adds this header to outgoing requests by using the provided `--client` and `--secret` values. diff --git a/guides/operation_store/active_record_backend.md b/guides/operation_store/active_record_backend.md index 8b36454343b..53d2bee48e5 100644 --- a/guides/operation_store/active_record_backend.md +++ b/guides/operation_store/active_record_backend.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: GraphQL Pro - OperationStore -title: ActiveRecord Backend -desc: Storing persisted queries with ActiveRecord -index: 2 -pro: true ---- +# ActiveRecord Backend 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. diff --git a/guides/operation_store/client_workflow.md b/guides/operation_store/client_workflow.md index 422375ec52f..7f915d45678 100644 --- a/guides/operation_store/client_workflow.md +++ b/guides/operation_store/client_workflow.md @@ -1,55 +1,46 @@ ---- -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 ---- +# Client Workflow To use persisted queries with your client application, you must: -- Set up `OperationStore`, as described in {% internal_link "Getting Started","/operation_store/getting_started" %} +- Set up `OperationStore`, as described in [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`. +This documentation also touches on [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" %}: +Clients are registered via [the dashboard](/operation_store/getting_started#add-routes): -{{ "/operation_store/add_a_client.png" | link_to_img:"Add a Client for Persisted Queries" }} +![Add a Client for Persisted Queries](/operation_store/add_a_client.png) -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" %}. +A default `secret` is provided for you, but you can also enter your own. The `secret` is used for [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`.) +(Are you interested in a Ruby API for this? Please [open an issue](https://github.com/rmosolgo/graphql-ruby/issues/new?title=OperationStore+Ruby+API&body=) 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" %}. +Once a client is registered, it can push queries to the server via [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" %}) +The easiest way to sync is with `graphql-ruby-client sync`, a command-line tool written in JavaScript ([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` +- Adds an [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" }} +![OperationStore client sync](/operation_store/sync_example.png) -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`. +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](https://github.com/rmosolgo/graphql-ruby/issues/new?title=Implementing+operation+sync+in+another+language&body=), 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. +See the [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: @@ -66,4 +57,4 @@ 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" %}. +Learn more about `OperationStore`'s [authentication](/operation_store/access_control) or read some tips for [server management](/operation_store/server_management). diff --git a/guides/operation_store/getting_started.md b/guides/operation_store/getting_started.md index 9303cac183f..b20aa5e38ca 100644 --- a/guides/operation_store/getting_started.md +++ b/guides/operation_store/getting_started.md @@ -1,13 +1,4 @@ ---- -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 ---- +# Getting Started To use `GraphQL::Pro::OperationStore` with your app, follow these steps: @@ -16,20 +7,20 @@ To use `GraphQL::Pro::OperationStore` with your app, follow these steps: - [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 +- [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!) +- [ActiveRecord](/operation_store/active_record_backend) or [Redis](/operation_store/redis_backend) for persistence. (Using another ORM or backend? Please [open an issue](https://github.com/rmosolgo/graphql-ruby/issues/new?title=Backend+support+request+for+OperationStore&body=) 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. +If you're going to store data with ActiveRecord, [migrate the database](/operation_store/active_record_backend) to prepare tables for it. ## Add `OperationStore` @@ -44,11 +35,11 @@ class MySchema < GraphQL::Schema end ``` -Make sure to add this feature _after_ other {% internal_link "Tracing", "/queries/tracing" %}-based features so that those other features will have access to the loaded query string. Otherwise, you may get `"No query string was present"` errors. +Make sure to add this feature _after_ other [Tracing](/queries/tracing)-based features so that those other features will have access to the loaded query string. Otherwise, you may get `"No query string was present"` errors. By default, it uses `ActiveRecord`. It also accepts: -- `redis:`, for using a {% internal_link "Redis backend", "/operation_store/redis_backend" %}; OR +- `redis:`, for using a [Redis backend](/operation_store/redis_backend); OR - `backend_class:`, for implementing custom persistence. Also, you can disable updates to "last used at" with `default_touch_last_used_at: false`. (This can also be configured per-query with `context[:operation_store_touch_last_used_at] = true|false`.) @@ -73,11 +64,11 @@ Rails.application.routes.draw do 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.operation_store_sync` receives pushes from clients. See [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. +`MySchema.dashboard` includes a web view to the `OperationStore`, visible at `/graphql/dashboard`. See the [Dashboard guide](/pro/dashboard) for more details, including authorization. -{{ "/operation_store/graphql_ui.png" | link_to_img:"GraphQL Persisted Operations Dashboard" }} +![GraphQL Persisted Operations Dashboard](/operation_store/graphql_ui.png) `operation_store_sync` and `dashboard` are both Rack apps, so you can mount them in Rails, Sinatra, or any other Rack app. @@ -92,7 +83,7 @@ mount lazy_routes.operation_store_sync, at: "/graphql/sync" ### With Visibility Profiles -You can apply a {% internal_link "visibility profile", "/authorization/visibility#visibility-profiles" %} to incoming operations by passing the profile name to `operation_store_sync`, for example: +You can apply a [visibility profile](/authorization/visibility#visibility-profiles) to incoming operations by passing the profile name to `operation_store_sync`, for example: ```ruby mount MySchema.operation_store_sync(visibility_profile: :public_api), at: "/graphql/sync" @@ -123,8 +114,8 @@ MySchema.execute( `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]`. +See [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" %}. +Sync your operations with the [Client Workflow](/operation_store/client_workflow). diff --git a/guides/operation_store/overview.md b/guides/operation_store/overview.md index 0b8cc5995d2..6679c2f845b 100644 --- a/guides/operation_store/overview.md +++ b/guides/operation_store/overview.md @@ -1,15 +1,6 @@ ---- -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/redis_backend" %}) to maintain a normalized, deduplicated database of _persisted queries_ for your GraphQL system. +# Overview + +`GraphQL::Pro::OperationStore` uses `Rack` and a storage backend ([ActiveRecord](/operation_store/active_record_backend) or [Redis](/operation_store/redis_backend)) to maintain a normalized, deduplicated database of _persisted queries_ for your GraphQL system. In this guide, you'll find: @@ -19,10 +10,10 @@ In this guide, you'll find: 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 +- [Getting Started](/operation_store/getting_started) installing `OperationStore` in your app +- [Workflow](/operation_store/client_workflow) and usage for client apps +- [Authentication](/operation_store/access_control) for the sync API +- [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). @@ -88,23 +79,23 @@ Persisted queries improve the _efficiency_ of your system by reducing HTTP traff 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" }} +![GraphQL request without persisted queries](/operation_store/request_before.png) 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" }} +![GraphQL request with persisted queries](/operation_store/request_after.png) ### 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" }} +![Index of GraphQL usage with persisted queries](/operation_store/operation_index.png) ## 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. +When clients [sync their operations](/operation_store/client_workflow), requests are [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`: @@ -116,4 +107,4 @@ params[:operationId] # => "relay-app-v1/810c97f6631001..." ## Getting Started -See the {% internal_link "getting started guide","/operation_store/getting_started" %} to add `OperationStore` to your app. +See the [getting started guide](/operation_store/getting_started) to add `OperationStore` to your app. diff --git a/guides/operation_store/redis_backend.md b/guides/operation_store/redis_backend.md index adc2fa590c5..1ba755fed08 100644 --- a/guides/operation_store/redis_backend.md +++ b/guides/operation_store/redis_backend.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: GraphQL Pro - OperationStore -title: Redis Backend -desc: Storing persisted queries with Redis -index: 3 -pro: true ---- +# Redis Backend `OperationStore` can use Redis to store persisted queries. Pass a `redis:` option when adding the plugin: diff --git a/guides/operation_store/server_management.md b/guides/operation_store/server_management.md index 7fe37854bc5..827f73a52ab 100644 --- a/guides/operation_store/server_management.md +++ b/guides/operation_store/server_management.md @@ -1,15 +1,6 @@ ---- -layout: guide -doc_stub: false -search: true -section: GraphQL Pro - OperationStore -title: Server Management -desc: Tips for administering persisted queries with OperationStore -index: 5 -pro: true ---- - -After {% internal_link "getting started","/operation_store/getting_started" %}, here some things to keep in mind. +# Server Management + +After [getting started](/operation_store/getting_started), here some things to keep in mind. ## Rejecting Arbitrary Queries @@ -55,7 +46,7 @@ MySchema.execute( ## Archiving and Deleting Data -Clients can only _add_ to the database, but as an administrator, you can also archive or delete entries from the database. (Make sure you {% internal_link "authorize access to the Dashboard","/pro/dashboard" %}.) This is a dangerous operation: by archiving or deleting something, any clients who depend on that data will crash. +Clients can only _add_ to the database, but as an administrator, you can also archive or delete entries from the database. (Make sure you [authorize access to the Dashboard](/pro/dashboard).) This is a dangerous operation: by archiving or deleting something, any clients who depend on that data will crash. Some reasons to archive or delete from the database are: @@ -74,4 +65,4 @@ It's on the road map to add a Ruby API to `OperationStore` so that you can integ - Show client secrets via the Dashboard so that users can save them - Render your own administration dashboards with `OperationStore` data -If this interests you, please {% open_an_issue "OperationStore Ruby API" %} or email `support@graphql.pro`. +If this interests you, please [open an issue](https://github.com/rmosolgo/graphql-ruby/issues/new?title=OperationStore+Ruby+API&body=) or email `support@graphql.pro`. diff --git a/guides/pagination/connection_concepts.md b/guides/pagination/connection_concepts.md index 74e77153ac6..684cd2cea2b 100644 --- a/guides/pagination/connection_concepts.md +++ b/guides/pagination/connection_concepts.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Pagination -title: Connection Concepts -desc: Introduction to Connections -index: 1 ---- +# Connection Concepts __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. @@ -52,7 +44,7 @@ Connections are often generated from object types. Their list items, called _nod ##### 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: +Connections can tell you about the list in general. For example, if you [add a total count field](/type_definitions/extensions#customizing-connections), they can tell you the count: ```ruby { diff --git a/guides/pagination/cursors.md b/guides/pagination/cursors.md index 820ac0fd340..f8faa9abb6b 100644 --- a/guides/pagination/cursors.md +++ b/guides/pagination/cursors.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Pagination -title: Cursors -desc: Advancing through lists with opaque cursors -index: 4 ---- +# Cursors Connections use _cursors_ to advance through paginated lists. A cursor is an opaque string that indicates a specific point in this. diff --git a/guides/pagination/custom_connections.md b/guides/pagination/custom_connections.md index d10058b07c5..66860924574 100644 --- a/guides/pagination/custom_connections.md +++ b/guides/pagination/custom_connections.md @@ -1,14 +1,6 @@ ---- -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. +# Custom Connections + +GraphQL-Ruby ships with built-in connection support for ActiveRecord, Sequel, Mongoid, and Ruby Arrays. You can read more in the [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: @@ -30,7 +22,7 @@ Your application probably has other list objects that you want to paginate via G 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: +You can extend [GraphQL::Pagination::Connection](rdoc-ref:GraphQL::Pagination::Connection) to get started on a custom connection wrapper, for example: ```ruby # app/graphql/connections/search_results_connection.rb @@ -48,10 +40,10 @@ The methods you must implement are: 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 }} +- [GraphQL::Pagination::ArrayConnection](rdoc-ref:GraphQL::Pagination::ArrayConnection) +- [GraphQL::Pagination::ActiveRecordRelationConnection](rdoc-ref:GraphQL::Pagination::ActiveRecordRelationConnection) +- [GraphQL::Pagination::SequelDatasetConnection](rdoc-ref:GraphQL::Pagination::SequelDatasetConnection) +- [GraphQL::Pagination::MongoidRelationConnection](rdoc-ref:GraphQL::Pagination::MongoidRelationConnection) ### Using a Custom Connection @@ -73,7 +65,7 @@ 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 }}): +Alternatively, you can apply a connection wrapper on a case-by-case basis by applying it during the resolver (method or [GraphQL::Schema::Resolver](rdoc-ref:GraphQL::Schema::Resolver)): ```ruby field :search, Types::SearchResult.connection_type, null: false do @@ -94,7 +86,7 @@ GraphQL-Ruby will use the provided connection wrapper in that case. You can use 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. +- [GraphQL::Types::Relay::BaseConnection](rdoc-ref:GraphQL::Types::Relay::BaseConnection) and [GraphQL::Types::Relay::BaseEdge](rdoc-ref:GraphQL::Types::Relay::BaseEdge) 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: diff --git a/guides/pagination/overview.md b/guides/pagination/overview.md index 0828af92ed1..a5347c992df 100644 --- a/guides/pagination/overview.md +++ b/guides/pagination/overview.md @@ -1,16 +1,5 @@ ---- -layout: guide -doc_stub: false -search: true -section: Pagination -title: Overview -desc: Introduction to pagination in GraphQL -index: 0 -redirect_from: - - /relay/connections/ ---- +# Overview - -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-Ruby ships with several implementations of Relay's "connection"-style pagination. You can familiarize yourself with connections in [Connection Concepts](/pagination/connection_concepts) and see how to use them in [Using Connections](/pagination/using_connections). It also supports custom connection implementations and type definitions, which you can explore in the [Custom Connections](/pagination/custom_connections) guide. GraphQL has its own [great pagination docs](https://graphql.org/learn/pagination/) for further reading. diff --git a/guides/pagination/stable_relation_connections.md b/guides/pagination/stable_relation_connections.md index 3c20dafa285..2643c8ee838 100644 --- a/guides/pagination/stable_relation_connections.md +++ b/guides/pagination/stable_relation_connections.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Pagination -title: Stable Relation Connections -desc: Advanced pagination for ActiveRecord -index: 4 -pro: true ---- +# Stable Relation Connections `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. @@ -15,7 +6,7 @@ These connection implementations are database-specific so that they can build pr ## 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. +The default [GraphQL::Pagination::ActiveRecordRelationConnection](rdoc-ref:GraphQL::Pagination::ActiveRecordRelationConnection) (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. diff --git a/guides/pagination/using_connections.md b/guides/pagination/using_connections.md index 9bd9e722833..1cd9a04ba07 100644 --- a/guides/pagination/using_connections.md +++ b/guides/pagination/using_connections.md @@ -1,14 +1,6 @@ ---- -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. +# Using Connections + +GraphQL-Ruby ships with a few implementations of the [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) and set the default number of items returned with [`default_page_size`](#default-page-size). @@ -54,7 +46,7 @@ The collection object (Array, Mongoid relation, Sequel dataset, ActiveRecord rel ## 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" %}. +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 [Custom Connections](/pagination/custom_connections). ## Special Cases diff --git a/guides/pro/dashboard.md b/guides/pro/dashboard.md index 0b3fbf22bc5..26fc5d9e954 100644 --- a/guides/pro/dashboard.md +++ b/guides/pro/dashboard.md @@ -1,16 +1,6 @@ ---- -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" %}. +# Dashboard + +[GraphQL-Pro](https://graphql.pro) includes a web dashboard for monitoring [Operation Store](/operation_store/overview) and [subscriptions](/subscriptions/pusher_implementation). diff --git a/guides/pro/encoders.md b/guides/pro/encoders.md index d2b7284b838..2b383f8071b 100644 --- a/guides/pro/encoders.md +++ b/guides/pro/encoders.md @@ -1,13 +1,4 @@ ---- -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 ---- +# Encrypted, Versioned Cursors and IDs `GraphQL::Pro` includes a mechanism for serving encrypted, versioned cursors and IDs. This provides some benefits: @@ -47,7 +38,7 @@ 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::Pagination::Connection#encode" | api_doc }} and {{ "GraphQL::Pagination::Connection#decode" | api_doc }}. +If you implement your own connections, you can access the encoder's encryption methods via `GraphQL::Pagination::Connection#encode` and `GraphQL::Pagination::Connection#decode`. ## Encrypting IDs diff --git a/guides/pro/home.md b/guides/pro/home.md index ad063c209cd..6101c794ad4 100644 --- a/guides/pro/home.md +++ b/guides/pro/home.md @@ -1,10 +1 @@ ---- -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::Pro Home diff --git a/guides/pro/installation.md b/guides/pro/installation.md index 2ae3fcdbe4a..cfeb1efdc74 100644 --- a/guides/pro/installation.md +++ b/guides/pro/installation.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: GraphQL Pro -title: Installation -desc: Get started with GraphQL::Pro -index: 1 -pro: true ---- +# Installation `GraphQL::Pro` is distributed as a Ruby gem. When you buy `GraphQL::Pro`, you'll receive credentials, which you can register with bundler: @@ -71,7 +62,7 @@ Validating graphql-pro v1.0.0 ✔ graphql-pro 1.0.0 validated successfully! ``` -In case of a failure, please {% open_an_issue "GraphQL Pro installation failure" %}: +In case of a failure, please [open an issue](https://github.com/rmosolgo/graphql-ruby/issues/new?title=GraphQL+Pro+installation+failure&body=): ``` Validating graphql-pro v1.4.800 diff --git a/guides/pro/privacy.md b/guides/pro/privacy.md index c56f8d83657..8512a8f14cc 100644 --- a/guides/pro/privacy.md +++ b/guides/pro/privacy.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: GraphQL Pro -title: Privacy -desc: Privacy Policy for GraphQL::Pro -index: 7 ---- +# Privacy The following statement describes what data GraphQL::Pro collects during normal operation and how that data is used. diff --git a/guides/queries/ast_analysis.md b/guides/queries/ast_analysis.md index 2025774f1e2..0a12d0ccda5 100644 --- a/guides/queries/ast_analysis.md +++ b/guides/queries/ast_analysis.md @@ -1,18 +1,8 @@ ---- -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/ ---- +# Ahead-of-Time AST Analysis You can do ahead-of-time analysis for your queries. -The primitive for analysis is {{ "GraphQL::Analysis::Analyzer" | api_doc }}. Analyzers must inherit from this base class and implement the desired methods for analysis. +The primitive for analysis is [GraphQL::Analysis::Analyzer](rdoc-ref:GraphQL::Analysis::Analyzer). Analyzers must inherit from this base class and implement the desired methods for analysis. ## Using Analyzers @@ -32,7 +22,7 @@ Analyzers respond to methods similar to AST visitors. They're named like `on_ent - `node`: The current AST node (being entered or left) - `parent`: The AST node which precedes this one in the tree -- `visitor`: A {{ "GraphQL::Analysis::Visitor" | api_doc }} which is managing this analysis run +- `visitor`: A [GraphQL::Analysis::Visitor](rdoc-ref:GraphQL::Analysis::Visitor) which is managing this analysis run For example: @@ -85,15 +75,15 @@ class BasicFieldAnalyzer < GraphQL::Analysis::Analyzer end ``` -See {{ "GraphQL::Analysis::Visitor" | api_doc }} for more information about the `visitor` object. +See [GraphQL::Analysis::Visitor](rdoc-ref:GraphQL::Analysis::Visitor) 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. +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](rdoc-ref:GraphQL::Query#arguments_for)). 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: +It is still possible to return errors from an analyzer. To reject a query and halt its execution, you may return [GraphQL::AnalysisError](rdoc-ref:GraphQL::AnalysisError) in the `result` method: ```ruby class NoFieldsCalledHello < GraphQL::Analysis::Analyzer diff --git a/guides/queries/backtrace_annotations.md b/guides/queries/backtrace_annotations.md index 4accf58194f..5eeb18032d5 100644 --- a/guides/queries/backtrace_annotations.md +++ b/guides/queries/backtrace_annotations.md @@ -1,13 +1,4 @@ ---- -title: Backtrace Annotations -layout: guide -doc_stub: false -search: true -section: Queries -desc: Use the GraphQL backtrace for debugging -index: 12 -experimental: true ---- +# Backtrace Annotations `context` objects have a `backtrace` which shows its GraphQL context. You can print the backtrace during query execution: diff --git a/guides/queries/complexity_and_depth.md b/guides/queries/complexity_and_depth.md index b3294be8e82..5d9ff8a2496 100644 --- a/guides/queries/complexity_and_depth.md +++ b/guides/queries/complexity_and_depth.md @@ -1,14 +1,6 @@ ---- -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. +# Complexity & Depth + +GraphQL-Ruby ships with some validations based on [query analysis](/queries/ast_analysis). You can customize them as-needed, too. ## Prevent deeply-nested queries @@ -34,7 +26,7 @@ You can use `nil` to disable the validation: MySchema.execute(query_string, max_depth: nil) ``` -To get a feeling for depth of queries in your system, you can extend {{ "GraphQL::Analysis::QueryDepth" | api_doc }}. Hook it up to log out values from each query: +To get a feeling for depth of queries in your system, you can extend [GraphQL::Analysis::QueryDepth](rdoc-ref:GraphQL::Analysis::QueryDepth). Hook it up to log out values from each query: ```ruby class LogQueryDepth < GraphQL::Analysis::QueryDepth @@ -100,7 +92,7 @@ Using `nil` will disable the validation: MySchema.execute(query_string, max_complexity: nil) ``` -To get a feeling for complexity of queries in your system, you can extend {{ "GraphQL::Analysis::QueryComplexity" | api_doc }}. Hook it up to log out values from each query: +To get a feeling for complexity of queries in your system, you can extend [GraphQL::Analysis::QueryComplexity](rdoc-ref:GraphQL::Analysis::QueryComplexity). Hook it up to log out values from each query: ```ruby class LogQueryComplexityAnalyzer < GraphQL::Analysis::QueryComplexity diff --git a/guides/queries/executing_queries.md b/guides/queries/executing_queries.md index 5694afbb9dd..152d117b690 100644 --- a/guides/queries/executing_queries.md +++ b/guides/queries/executing_queries.md @@ -1,15 +1,6 @@ ---- -layout: guide -doc_stub: false -search: true -section: Queries -title: Executing Queries -desc: Evaluate GraphQL queries with your schema -index: 0 ---- +# Executing Queries - -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: +You can execute queries with your [GraphQL::Schema](rdoc-ref:GraphQL::Schema) and get a Ruby Hash as a result. For example, to execute a query from a string: ```ruby query_string = "{ ... }" @@ -34,17 +25,16 @@ MySchema.multiplex([ # ] ``` -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-type-and-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 +The complete option contract for `variables:`, `context:`, `root_value:`, +`operation_name:`, `document:`, validation, and query limits is maintained in the +[GraphQL::Query](rdoc-ref:GraphQL::Query) and +[GraphQL::Schema](rdoc-ref:GraphQL::Schema) API references. The examples below +focus on the application-level patterns built on those options. -Some of these options are described in more detail below, see {{ "GraphQL::Query#initialize" | api_doc }} for more information. +The API-specific portions of this page were migrated to the +[GraphQL::Query](rdoc-ref:GraphQL::Query) and +[GraphQL::Schema](rdoc-ref:GraphQL::Schema) source comments. This page keeps +the variables, context, scoped-context, and root-value walkthroughs. ## Variables @@ -65,7 +55,7 @@ variables = { "postId" => "1" } MySchema.execute(query_string, variables: variables) ``` -If the variable is a {{ "GraphQL::Schema::InputObject" | api_doc }}, you can provide a nested hash, for example: +If the variable is a [GraphQL::Schema::InputObject](rdoc-ref:GraphQL::Schema::InputObject), you can provide a nested hash, for example: ```ruby query_string = " @@ -121,7 +111,7 @@ def post(id:) 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. +Note that `context` is _not_ the hash that you passed it. It's an instance of [GraphQL::Query::Context](rdoc-ref:GraphQL::Query::Context), but it delegates `#[]`, `#[]=`, and a few other methods to the hash you provide. ### Scoped Context @@ -143,16 +133,16 @@ However, "scoped context" can be used to assign values into `context` that are o You could use "scoped context" to implement `isOriginalPoster`, based on the parent `comments` field. -{% callout warning %} - -Using scoped context may result in a violation of [the GraphQL specification](https://spec.graphql.org/draft/#sel-EABDLDFAACHAo3V) and -break normalized client stores, which assume that a given object always -has the same values for its fields. - -See ["Referencing ancestors breaks normalized stores"](https://benjie.dev/graphql/ancestors#breaks-normalized-stores) -for details about this pitfall and alternative approaches which avoid it. +> **Warning:** +> +> Using scoped context may result in a violation of [the GraphQL specification](https://spec.graphql.org/draft/#sel-EABDLDFAACHAo3V) and +> break normalized client stores, which assume that a given object always +> has the same values for its fields. +> +> See ["Referencing ancestors breaks normalized stores"](https://benjie.dev/graphql/ancestors#breaks-normalized-stores) +> for details about this pitfall and alternative approaches which avoid it. +> -{% endcallout %} In `def comments`, add `:current_post` to scoped context using `context.scoped_set!`: @@ -214,4 +204,4 @@ class Types::MutationType < GraphQL::Schema::Object end ``` -{{ "GraphQL::Schema::Mutation" | api_doc }} fields will also receive `root_value:` as `obj` (assuming they're attached directly to your `MutationType`). +[GraphQL::Schema::Mutation](rdoc-ref:GraphQL::Schema::Mutation) fields will also receive `root_value:` as `obj` (assuming they're attached directly to your `MutationType`). diff --git a/guides/queries/logging.md b/guides/queries/logging.md index f50fb61cfcb..7e1ecf88527 100644 --- a/guides/queries/logging.md +++ b/guides/queries/logging.md @@ -1,16 +1,8 @@ ---- -layout: guide -doc_stub: false -search: true -section: Queries -title: Logging -desc: Development output from GraphQL-Ruby -index: 12 ---- +# Logging -At runtime, GraphQL-Ruby will output debug information using {{ "GraphQL::Query#logger" | api_doc }}. By default, this uses `Rails.logger`. To see output, make sure `config.log_level = :debug` is set. (This information isn't meant for production logs.) +At runtime, GraphQL-Ruby will output debug information using [GraphQL::Query#logger](rdoc-ref:GraphQL::Query#logger). By default, this uses `Rails.logger`. To see output, make sure `config.log_level = :debug` is set. (This information isn't meant for production logs.) -You can configure a custom logger with {{ "GraphQL::Schema.default_logger" | api_doc }}, for example: +You can configure a custom logger with [GraphQL::Schema.default_logger](rdoc-ref:GraphQL::Schema.default_logger), for example: ```ruby class MySchema < GraphQL::Schema diff --git a/guides/queries/lookahead.md b/guides/queries/lookahead.md index 8a5541d4851..f1ddc51cd13 100644 --- a/guides/queries/lookahead.md +++ b/guides/queries/lookahead.md @@ -1,14 +1,6 @@ ---- -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. +# Lookahead + +GraphQL-Ruby 1.9+ includes [GraphQL::Execution::Lookahead](rdoc-ref:GraphQL::Execution::Lookahead) 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 diff --git a/guides/queries/multiplex.md b/guides/queries/multiplex.md index 9e3d1301485..ef62d4a3f77 100644 --- a/guides/queries/multiplex.md +++ b/guides/queries/multiplex.md @@ -1,14 +1,6 @@ ---- -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 + +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](rdoc-ref:GraphQL::Schema.multiplex). Multiplex runs have their own context, analyzers and instrumentation. @@ -84,11 +76,11 @@ 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. +Each query is validated and [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: +You can add values to `Execution::Multiplex#context` by providing a `context:` hash: ```ruby MySchema.multiplex(queries, context: { current_user: current_user }) @@ -107,15 +99,15 @@ class MySchema < GraphQL::Schema end ``` -The API is the same as {% internal_link "query analyzers","/queries/ast_analysis#analyzing-multiplexes" %}. +The API is the same as [query analyzers](/queries/ast_analysis#analyzing-multiplexes). -Multiplex analyzers may return {{ "AnalysisError" | api_doc }} to halt execution of the whole multiplex. +Multiplex analyzers may return [GraphQL::AnalysisError](rdoc-ref:GraphQL::AnalysisError) to halt execution of the whole multiplex. ## Multiplex Tracing -You can add hooks for each multiplex run with {% internal_link "trace modules", "/queries/tracing" %}. +You can add hooks for each multiplex run with [trace modules](/queries/tracing). -The trace module may implement `def execute_multiplex(multiplex:)` which calls `super` to allow the multiplex to execute. See {{ "Execution::Multiplex" | api_doc }} for available methods. +The trace module may implement `def execute_multiplex(multiplex:)` which calls `super` to allow the multiplex to execute. See `Execution::Multiplex` for available methods. For example: diff --git a/guides/queries/phases_of_execution.md b/guides/queries/phases_of_execution.md index e75989a3ea0..d1fd6d8c75c 100644 --- a/guides/queries/phases_of_execution.md +++ b/guides/queries/phases_of_execution.md @@ -1,18 +1,10 @@ ---- -layout: guide -doc_stub: false -search: true -section: Queries -title: Phases of Execution -desc: The steps GraphQL takes to run your query -index: 2 ---- +# Phases of Execution When GraphQL receives a query string, it goes through these steps: -- Tokenize: {{ "GraphQL::Language::Lexer" | api_doc }} splits the string into a stream of tokens -- Parse: {{ "GraphQL::Language::Parser" | api_doc }} builds an abstract syntax tree (AST) out of the stream of tokens -- Validate: {{ "GraphQL::StaticValidation::Validator" | api_doc }} validates the incoming AST as a valid query for the schema -- Analyze: If there are any query analyzers, they are run with {{ "GraphQL::Analysis.analyze_query" | api_doc }} +- Tokenize: [GraphQL::Language::Lexer](rdoc-ref:GraphQL::Language::Lexer) splits the string into a stream of tokens +- Parse: [GraphQL::Language::Parser](rdoc-ref:GraphQL::Language::Parser) builds an abstract syntax tree (AST) out of the stream of tokens +- Validate: [GraphQL::StaticValidation::Validator](rdoc-ref:GraphQL::StaticValidation::Validator) validates the incoming AST as a valid query for the schema +- Analyze: If there are any query analyzers, they are run with [GraphQL::Analysis.analyze_query](rdoc-ref:GraphQL::Analysis.analyze_query) - Execute: The query is traversed, `resolve` functions are called and the response is built -- Respond: The response is returned as a {{ "GraphQL::Query::Result" | api_doc }} +- Respond: The response is returned as a [GraphQL::Query::Result](rdoc-ref:GraphQL::Query::Result) diff --git a/guides/queries/response_extensions.md b/guides/queries/response_extensions.md index 5602de450fe..f11ba2d6daa 100644 --- a/guides/queries/response_extensions.md +++ b/guides/queries/response_extensions.md @@ -1,12 +1,4 @@ ---- -title: Response Extensions -layout: guide -doc_stub: false -search: true -section: Queries -desc: Adding "extensions" to the response hash -index: 12 ---- +# Response Extensions During query execution, you can add to the response's `"extensions" => { ... }` Hash. By default, no `"extensions"` key is present in the result, but if you call the method below, it will be present with the given values. diff --git a/guides/queries/timeout.md b/guides/queries/timeout.md index d72c9a7d890..429f75b2b19 100644 --- a/guides/queries/timeout.md +++ b/guides/queries/timeout.md @@ -1,12 +1,4 @@ ---- -title: Timeout -layout: guide -doc_stub: false -search: true -section: Queries -desc: Cutting off GraphQL execution -index: 5 ---- +# Timeout You can apply a timeout to query execution with the `GraphQL::Schema::Timeout` plugin. For example: @@ -38,7 +30,7 @@ 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`. +To dynamically pick a timeout duration (or bypass it), override [GraphQL::Schema::Timeout#max_seconds](rdoc-ref:GraphQL::Schema::Timeout#max_seconds) in your subclass. To bypass the timeout altogether, `max_seconds` can return `false`. For example: diff --git a/guides/queries/tracing.md b/guides/queries/tracing.md index 25612676bc9..9a7c2105863 100644 --- a/guides/queries/tracing.md +++ b/guides/queries/tracing.md @@ -1,16 +1,6 @@ ---- -title: Tracing -layout: guide -doc_stub: false -search: true -section: Queries -desc: Observation hooks for execution -index: 11 -redirect_from: - - /queries/instrumentation ---- - -{{ "GraphQL::Tracing::Trace" | api_doc }} provides hooks to observe and modify events during runtime. Tracing hooks are methods, defined in modules and mixed in with {{ "Schema.trace_with" | api_doc }}. +# Tracing + +[GraphQL::Tracing::Trace](rdoc-ref:GraphQL::Tracing::Trace) provides hooks to observe and modify events during runtime. Tracing hooks are methods, defined in modules and mixed in with [Schema.trace_with](rdoc-ref:GraphQL::Schema.trace_with). ```ruby module CustomTrace @@ -32,29 +22,29 @@ class MySchema < GraphQL::Schema end ``` -For a full list of methods and their arguments, see {{ "GraphQL::Tracing::Trace" | api_doc }}. +For a full list of methods and their arguments, see [GraphQL::Tracing::Trace](rdoc-ref:GraphQL::Tracing::Trace). By default, GraphQL-Ruby makes a new trace instance when it runs a query. You can pass an existing instance as `context: { trace: ... }`. Also, `GraphQL.parse( ..., trace: ...)` accepts a trace instance. ## Detailed Traces -You can capture detailed traces of query execution with {{ "Tracing::DetailedTrace" | api_doc }}. They can be viewed in Google's [Perfetto Trace Viewer](https://ui.perfetto.dev). They include a per-Fiber breakdown with links between fields and Dataloader sources. +You can capture detailed traces of query execution with [Tracing::DetailedTrace](rdoc-ref:GraphQL::Tracing::DetailedTrace). They can be viewed in Google's [Perfetto Trace Viewer](https://ui.perfetto.dev). They include a per-Fiber breakdown with links between fields and Dataloader sources. -{{ "/queries/perfetto_example.png" | link_to_img:"GraphQL-Ruby Dataloader Perfetto Trace" }} +![GraphQL-Ruby Dataloader Perfetto Trace](/queries/perfetto_example.png) -Learn how to set it up in the {{ "Tracing::DetailedTrace" | api_doc }} docs. +Learn how to set it up in the [Tracing::DetailedTrace](rdoc-ref:GraphQL::Tracing::DetailedTrace) docs. ## External Monitoring Platforms There integrations for GraphQL-Ruby with several other monitoring systems: -- `ActiveSupport::Notifications`: See {{ "Tracing::ActiveSupportNotificationsTrace" | api_doc }}. +- `ActiveSupport::Notifications`: See [Tracing::ActiveSupportNotificationsTrace](rdoc-ref:GraphQL::Tracing::ActiveSupportNotificationsTrace). - [AppOptics](https://appoptics.com/) instrumentation is automatic in `appoptics_apm` v4.11.0+. -- [AppSignal](https://appsignal.com/): See {{ "Tracing::AppsignalTrace" | api_doc }}. -- [Datadog](https://www.datadoghq.com): See {{ "Tracing::DataDogTrace" | api_doc }}. -- [NewRelic](https://newrelic.com/): See {{ "Tracing::NewRelicTrace" | api_doc }}. -- [Prometheus](https://prometheus.io): See {{ "Tracing::PrometheusTrace" | api_doc }}. -- [Scout APM](https://www.scoutapm.com/): See {{ "Tracing::ScoutTrace" | api_doc }}. -- [Sentry](https://sentry.io): See {{ "Tracing::SentryTrace" | api_doc }}. -- [Skylight](https://www.skylight.io): either enable the [GraphQL probe](https://www.skylight.io/support/getting-more-from-skylight#graphql) or use {{ "Tracing::ActiveSupportNotificationsTrace" | api_doc }}. -- Statsd: See {{ "Tracing::StatsdTrace" | api_doc }}. +- [AppSignal](https://appsignal.com/): See [Tracing::AppsignalTrace](rdoc-ref:GraphQL::Tracing::AppsignalTrace). +- [Datadog](https://www.datadoghq.com): See [Tracing::DataDogTrace](rdoc-ref:GraphQL::Tracing::DataDogTrace). +- [NewRelic](https://newrelic.com/): See [Tracing::NewRelicTrace](rdoc-ref:GraphQL::Tracing::NewRelicTrace). +- [Prometheus](https://prometheus.io): See [Tracing::PrometheusTrace](rdoc-ref:GraphQL::Tracing::PrometheusTrace). +- [Scout APM](https://www.scoutapm.com/): See [Tracing::ScoutTrace](rdoc-ref:GraphQL::Tracing::ScoutTrace). +- [Sentry](https://sentry.io): See [Tracing::SentryTrace](rdoc-ref:GraphQL::Tracing::SentryTrace). +- [Skylight](https://www.skylight.io): either enable the [GraphQL probe](https://www.skylight.io/support/getting-more-from-skylight#graphql) or use [Tracing::ActiveSupportNotificationsTrace](rdoc-ref:GraphQL::Tracing::ActiveSupportNotificationsTrace). +- Statsd: See [Tracing::StatsdTrace](rdoc-ref:GraphQL::Tracing::StatsdTrace). diff --git a/guides/related_projects.md b/guides/related_projects.md index dff3e99c646..9d6cd464037 100644 --- a/guides/related_projects.md +++ b/guides/related_projects.md @@ -1,11 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -title: Related Projects -section: Other -desc: Code, blog posts and presentations about GraphQL Ruby ---- +# Related Projects Want to add something? Please open a pull request [on GitHub](https://github.com/rmosolgo/graphql-ruby)! diff --git a/guides/relay/range_add.md b/guides/relay/range_add.md index fd81f922e8d..6357f3b76d3 100644 --- a/guides/relay/range_add.md +++ b/guides/relay/range_add.md @@ -1,11 +1,3 @@ ---- -layout: guide -doc_stub: false -search: true -section: Relay -title: RangeAdd helper for mutations -desc: A helper for Relay's RANGE_ADD operations -index: 1 ---- +# RangeAdd helper for mutations -Relay specifies `RANGE_ADD` operations for adding items to connections. GraphQL-Ruby ships with {{ "GraphQL::Relay::RangeAdd" | api_doc }} to help implement this. Check the API docs for a usage example. +Relay specifies `RANGE_ADD` operations for adding items to connections. GraphQL-Ruby ships with [GraphQL::Relay::RangeAdd](rdoc-ref:GraphQL::Relay::RangeAdd) to help implement this. Check the API docs for a usage example. diff --git a/guides/schema/definition.md b/guides/schema/definition.md index d9038b3d3e7..401e3fb96d1 100644 --- a/guides/schema/definition.md +++ b/guides/schema/definition.md @@ -1,17 +1,8 @@ ---- -layout: guide -doc_stub: false -search: true -section: Schema -title: Definition -desc: Defining your schema -index: 1 ---- +# Definition +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 [introspection system](/schema/introspection). -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: +Your GraphQL schema is a class that extends [GraphQL::Schema](rdoc-ref:GraphQL::Schema), for example: ```ruby class MyAppSchema < GraphQL::Schema @@ -34,20 +25,30 @@ class MyAppSchema < GraphQL::Schema end ``` -There are lots of schema configuration methods. +There are lots of schema configuration methods. The complete reference is +maintained with the implementation in [GraphQL::Schema](rdoc-ref:GraphQL::Schema), +including root types, object identification, error hooks, limits, introspection, +authorization, tracing, analyzers, and plugins. + +The API-specific portions of this page were migrated to the +[GraphQL::Schema](rdoc-ref:GraphQL::Schema) source comments. This page keeps the +setup, lazy-loading, and production walkthroughs. -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" %}. +For defining GraphQL types, see the guides for those types: [object types](/type_definitions/objects), [interface types](/type_definitions/interfaces), [union types](/type_definitions/unions), [input object types](/type_definitions/input_objects), [enum types](/type_definitions/enums), and [scalar types](/type_definitions/scalars). ## Types in the Schema -- {{ "Schema.query" | api_doc }}, {{ "Schema.mutation" | api_doc }}, and {{ "Schema.subscription" | api_doc}} declare the [entry-point types](https://graphql.org/learn/schema/#the-query-mutation-and-subscription-types) of the schema. -- {{ "Schema.orphan_types" | api_doc }} declares object types which implement {% internal_link "Interfaces", "/type_definitions/interfaces" %} but aren't used as field return types in the schema. For more about this specific scenario, see {% internal_link "Orphan Types", "/type_definitions/interfaces#orphan-types" %} +See the [GraphQL::Schema API reference](rdoc-ref:GraphQL::Schema) for +root types, orphan types, and extra types. For defining GraphQL types, see the +guides for [object types](/type_definitions/objects), [interface types](/type_definitions/interfaces), +[union types](/type_definitions/unions), [input objects](/type_definitions/input_objects), +[enums](/type_definitions/enums), and [scalars](/type_definitions/scalars). ### Lazy-loading types In development, GraphQL-Ruby can defer loading your type definitions until they're needed. This requires some configuration to opt in: -- Add `use GraphQL::Schema::Visibility` to your schema. ({{ "GraphQL::Schema::Visibility" | api_doc }} supports lazy loading and will be the default in a future GraphQL-Ruby version. See {% internal_link "Migration Notes", "/authorization/visibility#migration-notes" %} if you have an existing visibility implementation.) +- Add `use GraphQL::Schema::Visibility` to your schema. ([GraphQL::Schema::Visibility](rdoc-ref:GraphQL::Schema::Visibility) supports lazy loading and will be the default in a future GraphQL-Ruby version. See [Migration Notes](/authorization/visibility#migration-notes) if you have an existing visibility implementation.) - Move your entry-point type definitions into a block, for example: ```diff @@ -71,56 +72,54 @@ To enforce these patterns, you can enable two Rubocop rules that ship with Graph ## Object Identification -Some GraphQL features use unique IDs to load objects: - -- the `node(id:)` field looks up objects by ID (See {% internal_link "Object Identification", "/schema/object_identification" %} for more about Relay-style object identification.) -- any arguments with `loads:` configurations look up objects by ID -- the {% internal_link "ObjectCache", "/object_cache/overview" %} uses IDs in its caching scheme - -To use these features, you must provide some methods for generating UUIDs and fetching objects with them: - -{{ "Schema.object_from_id" | api_doc }} is called by GraphQL-Ruby to load objects directly from the database. It's usually used by the `node(id: ID!): Node` field (see {{ "GraphQL::Types::Relay::Node" | api_doc }}), Argument {% internal_link "loads:", "/mutations/mutation_classes#auto-loading-arguments" %}, or the {% internal_link "ObjectCache", "/object_cache/overview" %}. 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). - -{{ "Schema.id_from_object" | api_doc }} is used to implement `Node.id`. It should return a unique ID for the given object. This ID will later be sent to `object_from_id` to refetch the object. - -Additionally, {{ "Schema.resolve_type" | api_doc }} is called by GraphQL-Ruby to get the runtime Object type for fields that return {% internal_link "interface", "/type_definitions/interfaces" %} or {% internal_link "union", "/type_definitions/unions" %} types. +The [GraphQL::Schema API reference](rdoc-ref:GraphQL::Schema) +documents `object_from_id`, `id_from_object`, and `resolve_type`. For Relay-style +IDs, see [Object Identification](/schema/object_identification); for `loads:`, see +[auto-loading arguments](/mutations/mutation_classes#auto-loading-arguments). ## Error Handling -- {{ "Schema.type_error" | api_doc }} handles type errors at runtime, read more in the {% internal_link "Type errors guide", "/errors/type_errors" %}. -- {{ "Schema.rescue_from" | api_doc }} defines error handlers for application errors. See the {% internal_link "error handling guide", "/errors/error_handling" %} for more. -- {{ "Schema.parse_error" | api_doc }} and {{ "Schema.query_stack_error" | api_doc }} provide hooks for reporting errors to your bug tracker. +The [GraphQL::Schema API reference](rdoc-ref:GraphQL::Schema) +describes the error hooks. See the [Type errors guide](/errors/type_errors) and +[error handling guide](/errors/error_handling) for application-level examples. ## Default Limits -- {{ "Schema.max_depth" | api_doc }} and {{ "Schema.max_complexity" | api_doc }} apply some limits to incoming queries. See {% internal_link "Complexity and Depth", "/queries/complexity_and_depth" %} for more. -- {{ "Schema.default_max_page_size" | api_doc }} applies limits to {% internal_link "connection fields", "/pagination/overview" %}. -- {{ "Schema.validate_timeout" | api_doc }}, {{ "Schema.validate_max_errors" | api_doc }} and {{ "Schema.max_query_string_tokens" | api_doc }} all apply limits to query execution. See {% internal_link "Timeout", "/queries/timeout" %} for more. +See the [GraphQL::Schema API reference](rdoc-ref:GraphQL::Schema) +for depth, complexity, page-size, timeout, error-count, and query-token limits. +The [Complexity and Depth](/queries/complexity_and_depth) and [Timeout](/queries/timeout) +guides show deployment-oriented examples. ## Introspection -- {{ "Schema.extra_types" | api_doc }} declares types which should be printed in the SDL and returned in introspection queries, but aren't otherwise used in the schema. -- {{ "Schema.introspection" | api_doc }} can attach a {% internal_link "custom introspection system", "/schema/introspection" %} to the schema. +The [GraphQL::Schema API reference](rdoc-ref:GraphQL::Schema) +documents `extra_types` and custom introspection namespaces. See the +[introspection guide](/schema/introspection) for a custom introspection system. ## Authorization -- {{ "Schema.unauthorized_object" | api_doc }} and {{ "Schema.unauthorized_field" | api_doc }} are called when {% internal_link "authorization hooks", "/authorization/authorization" %} return `false` during query execution. +The [GraphQL::Schema API reference](rdoc-ref:GraphQL::Schema) +documents the unauthorized object and field hooks. See the +[authorization guide](/authorization/authorization) for the complete authorization flow. ## Execution Configuration -- {{ "Schema.trace_with" | api_doc }} attaches tracer modules. See {% internal_link "Tracing", "/queries/tracing" %} for more. -- {{ "Schema.query_analyzer" | api_doc }} and {{ "Schema.multiplex_analyzer" }} accept processors for ahead-of-time query analysis, see {% internal_link "Analysis", "/queries/ast_analysis" %} for more. -- {{ "Schema.default_logger" | api_doc }} configures a logger for runtime. See {% internal_link "Logging", "/queries/logging" %}. -- {{ "Schema.context_class" | api_doc }} and {{ "Schema.query_class" | api_doc }} attach custom subclasses to your schema to use during execution. -- {{ "Schema.lazy_resolve" | api_doc }} registers classes with {% internal_link "lazy execution", "/schema/lazy_execution" %}. +The [GraphQL::Schema API reference](rdoc-ref:GraphQL::Schema) +documents tracing, analyzers, logging, custom execution classes, lazy values, and +plugins. See [Tracing](/queries/tracing), [Analysis](/queries/ast_analysis), +[Logging](/queries/logging), and [lazy execution](/schema/lazy_execution) for +cross-cutting examples. ## Plugins -- {{ "Schema.use" | api_doc }} adds plugins to your schema. For example, {{ "GraphQL::Dataloader" | api_doc }} and {{ "GraphQL::Schema::Visibility" | api_doc }} are installed this way. +Use [Schema.use](rdoc-ref:GraphQL::Schema.use) to install plugins such as +[GraphQL::Dataloader](rdoc-ref:GraphQL::Dataloader) and +[GraphQL::Schema::Visibility](rdoc-ref:GraphQL::Schema::Visibility). The plugin +contract is documented in the [GraphQL::Schema API reference](rdoc-ref:GraphQL::Schema). ## Production Considerations -- __Parser caching__: if your application parses GraphQL _files_ (queries or schema definition), it may benefit from enabling {{ "GraphQL::Language::Cache" | api_doc }}. +- __Parser caching__: if your application parses GraphQL _files_ (queries or schema definition), it may benefit from enabling [GraphQL::Language::Cache](rdoc-ref:GraphQL::Language::Cache). - __Eager loading the library__: by default, GraphQL-Ruby autoloads its constants as-needed. In production, they should be eager loaded instead, using `GraphQL.eager_load!`. - Rails: enabled automatically. (ActiveSupport calls `.eager_load!`.) @@ -128,4 +127,4 @@ Additionally, {{ "Schema.resolve_type" | api_doc }} is called by GraphQL-Ruby to - Hanami: add `environment(:production) { GraphQL.eager_load! }` to your application file. - Other frameworks: call `GraphQL.eager_load!` when your application is booting in production mode. - See {{"GraphQL::Autoload#eager_load!" | api_doc }} for more details. + See [GraphQL::Autoload#eager_load!](rdoc-ref:GraphQL::Autoload#eager_load!) for more details. diff --git a/guides/schema/dynamic_types.md b/guides/schema/dynamic_types.md index 9497b5d2e86..6bc0cbef9e3 100644 --- a/guides/schema/dynamic_types.md +++ b/guides/schema/dynamic_types.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Schema -title: Dynamic types and fields -desc: Using different schema members for each request -index: 8 ---- +# Dynamic types and fields You can use different versions of your GraphQL schema for each operation. To do this, add `use GraphQL::Schema::Visibility` and implement `visible?(context)` on the parts of your schema that will be conditionally accessible. Additionally, many schema elements have definition methods which are called at runtime by GraphQL-Ruby. You can re-implement those to return any valid schema objects. @@ -23,7 +15,7 @@ You can customize which field definitions are used for each operation. ### Using `#visible?(context)` -To serve different fields to different clients, implement `def visible?(context)` in your {% internal_link "base field class", "/type_definitions/extensions#customizing-fields" %}: +To serve different fields to different clients, implement `def visible?(context)` in your [base field class](/type_definitions/extensions#customizing-fields): ```ruby class Types::BaseField < GraphQL::Schema::Field @@ -98,7 +90,7 @@ As with fields, you can use different sets of argument definitions for different ### Using `#visible?(context)` -To serve different arguments to different clients, implement `def visible?(context)` in your {% internal_link "base argument class", "/type_definitions/extensions#customizing-arguments" %}: +To serve different arguments to different clients, implement `def visible?(context)` in your [base argument class](/type_definitions/extensions#customizing-arguments): ```ruby class Types::BaseArgument < GraphQL::Schema::Argument @@ -140,7 +132,7 @@ That way, any staff client will have the option of `id` or `databaseId` while no ### Using `def arguments(context)` and `def get_argument(name, context)` -Also, you can implement `def arguments(context)` on your base field class to return a Hash of `{ String => GraphQL::Schema::Argument }` and `def get_argument(name, context)` to return a {{ "GraphQL::Schema::Argument" | api_doc }} or `nil`. . If you take this approach, you might want some custom field classes for any types or resolvers that use these methods. That way, you don't have to reimplement the method for _all_ the fields in the schema. +Also, you can implement `def arguments(context)` on your base field class to return a Hash of `{ String => GraphQL::Schema::Argument }` and `def get_argument(name, context)` to return a [GraphQL::Schema::Argument](rdoc-ref:GraphQL::Schema::Argument) or `nil`. . If you take this approach, you might want some custom field classes for any types or resolvers that use these methods. That way, you don't have to reimplement the method for _all_ the fields in the schema. ### Hidden Input Types @@ -150,7 +142,7 @@ Besides argument visibility described above, if an argument's input type is hidd ### Using `#visible?(context)` -You can implement `def visible?(context)` in your {% internal_link "base enum value class", "/type_definitions/extensions#customizing-enum-values" %} to hide some enum values from some clients. For example: +You can implement `def visible?(context)` in your [base enum value class](/type_definitions/extensions#customizing-enum-values) to hide some enum values from some clients. For example: ```ruby class BaseEnumValue < GraphQL::Schema::EnumValue @@ -189,7 +181,7 @@ end ### Using `.enum_values(context)` -Alternatively, you can implement `def self.enum_values(context)` in your enum types to return an Array of {{ "GraphQL::Schema::EnumValue" | api_doc }}s. For example, to return a dynamic set of enum values: +Alternatively, you can implement `def self.enum_values(context)` in your enum types to return an Array of [GraphQL::Schema::EnumValue](rdoc-ref:GraphQL::Schema::EnumValue)s. For example, to return a dynamic set of enum values: ```ruby class ProjectStatus < Types::BaseEnum @@ -297,7 +289,7 @@ Input types (like input objects, scalars, and enums) work the same way with argu ## Schema Dumps -To dump a certain _version_ of the schema, provide the applicable `context: ...` to {{ "Schema.to_definition" | api_doc }}. For example: +To dump a certain _version_ of the schema, provide the applicable `context: ...` to [Schema.to_definition](rdoc-ref:GraphQL::Schema.to_definition). For example: ```ruby # Legacy money schema: diff --git a/guides/schema/generators.md b/guides/schema/generators.md index 433d8ca3bad..2ec5cac40f9 100644 --- a/guides/schema/generators.md +++ b/guides/schema/generators.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -title: Generators -section: Schema -desc: Use Rails generators to install GraphQL and scaffold new types. -index: 3 ---- +# Generators If you're using GraphQL with Ruby on Rails, you can use generators to: @@ -33,7 +25,7 @@ This will: - 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) -- Enable [`ActiveRecord::QueryLogs`](https://api.rubyonrails.org/classes/ActiveRecord/QueryLogs.html) and add GraphQL-related metadata (using {{ "GraphQL::Current" | api_doc }}) +- Enable [`ActiveRecord::QueryLogs`](https://api.rubyonrails.org/classes/ActiveRecord/QueryLogs.html) and add GraphQL-related metadata (using [GraphQL::Current](rdoc-ref:GraphQL::Current)) After installing you can see your new schema by: diff --git a/guides/schema/introspection.md b/guides/schema/introspection.md index 688086823ae..97d0eb5e9fb 100644 --- a/guides/schema/introspection.md +++ b/guides/schema/introspection.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -title: Introspection -section: Schema -desc: GraphQL has an introspection system that tells about the schema. -index: 3 ---- +# Introspection 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: @@ -111,9 +103,9 @@ Keep in mind that off-the-shelf tooling may not support your custom introspectio 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`.) +- Class-based [object definitions](/type_definitions/objects) which replace the built-in introspection types (such as `__Schema` and `__Type`) +- `EntryPoints`, A class-based [object definition](/type_definitions/objects) containing introspection entry points (like `__schema` and `__type(name:)`). +- `DynamicFields`, A class-based [object definition](/type_definitions/objects) containing dynamic, globally-available fields (like `__typename`.) ### Custom Introspection Types @@ -121,14 +113,14 @@ The `module` passed as `introspection` may contain classes with the following na 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 }} +`SchemaType` | `__Schema` | [GraphQL::Introspection::SchemaType](rdoc-ref:GraphQL::Introspection::SchemaType) +`TypeType` | `__Type` | [GraphQL::Introspection::TypeType](rdoc-ref:GraphQL::Introspection::TypeType) +`DirectiveType` | `__Directive` | [GraphQL::Introspection::DirectiveType](rdoc-ref:GraphQL::Introspection::DirectiveType) +`DirectiveLocationType` | `__DirectiveLocation` | [GraphQL::Introspection::DirectiveLocationEnum](rdoc-ref:GraphQL::Introspection::DirectiveLocationEnum) +`EnumValueType` | `__EnumValue` | [GraphQL::Introspection::EnumValueType](rdoc-ref:GraphQL::Introspection::EnumValueType) +`FieldType` | `__Field` | [GraphQL::Introspection::FieldType](rdoc-ref:GraphQL::Introspection::FieldType) +`InputValueType` | `__InputValue` | [GraphQL::Introspection::InputValueType](rdoc-ref:GraphQL::Introspection::InputValueType) +`TypeKindType` | `__TypeKind` | [GraphQL::Introspection::TypeKindEnum](rdoc-ref:GraphQL::Introspection::TypeKindEnum) The class-based definitions' names _must_ match the names of the types they replace. diff --git a/guides/schema/lazy_execution.md b/guides/schema/lazy_execution.md index 2e5cdaad421..fa3014161c9 100644 --- a/guides/schema/lazy_execution.md +++ b/guides/schema/lazy_execution.md @@ -1,17 +1,9 @@ ---- -layout: guide -doc_stub: false -search: true -title: Lazy Execution -section: Schema -desc: Resolvers can return "unfinished" results that are deferred for batch resolution. -index: 4 ---- +# Lazy Execution 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 }} +- Connect it to your schema with [GraphQL::Schema.lazy_resolve](rdoc-ref:GraphQL::Schema.lazy_resolve) - In `resolve` methods, return instances of the lazy-loading class ## Example: Batched Find @@ -90,7 +82,7 @@ Will only make one query to load the `author` values. The example above is simple and has some shortcomings. Consider the following gems for a robust solution to batched resolution: -* {{ "GraphQL::Dataloader" | api_doc }} is a built-in, Fiber-based approach to batching. See the {% internal_link "Dataloader guide", "/dataloader/overview" %} for more information. +* [GraphQL::Dataloader](rdoc-ref:GraphQL::Dataloader) is a built-in, Fiber-based approach to batching. See the [Dataloader guide](/dataloader/overview) for more information. * [`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. diff --git a/guides/schema/object_identification.md b/guides/schema/object_identification.md index 9ae80c4d8d1..274d98fd4eb 100644 --- a/guides/schema/object_identification.md +++ b/guides/schema/object_identification.md @@ -1,24 +1,16 @@ ---- -layout: guide -doc_stub: false -search: true -title: Object Identification -section: Schema -desc: Working with unique global IDs -index: 8 ---- +# Object Identification GraphQL-Ruby ships with some helpers to implement [Relay-style object identification](https://relay.dev/graphql/objectidentification.htm). ## Schema methods -See {% internal_link "the Schema definition guide", "/schema/definition#object-identification" %} for required top-level hooks. +See [the Schema definition guide](/schema/definition#object-identification) for required top-level hooks. ## 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: +To implement the node interface, add [GraphQL::Types::Relay::Node](rdoc-ref:GraphQL::Types::Relay::Node) to your definition: ```ruby class Types::PostType < GraphQL::Schema::Object @@ -51,7 +43,7 @@ end Nodes must have a field named `"id"` which returns a globally unique ID. -To add a UUID field named `"id"`, implement the {{ "GraphQL::Types::Relay::Node" | api_doc }} interface:: +To add a UUID field named `"id"`, implement the [GraphQL::Types::Relay::Node](rdoc-ref:GraphQL::Types::Relay::Node) interface:: ```ruby class Types::PostType < GraphQL::Schema::Object diff --git a/guides/schema/root_types.md b/guides/schema/root_types.md index c0bb1df703e..6b90818d0dd 100644 --- a/guides/schema/root_types.md +++ b/guides/schema/root_types.md @@ -1,12 +1,4 @@ ---- -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 ---- +# Root Types GraphQL queries begin from [root types](https://graphql.org/learn/schema/#the-query-mutation-and-subscription-types): `query`, `mutation`, and `subscription`. diff --git a/guides/schema/sdl.md b/guides/schema/sdl.md index db2884cf9b0..df7ed77754c 100644 --- a/guides/schema/sdl.md +++ b/guides/schema/sdl.md @@ -1,14 +1,6 @@ ---- -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: +# Parsing GraphQL Schema Definition Language into a Ruby Schema + +GraphQL-Ruby includes a way to build a runable schema from the GraphQL Schema Definition Language (SDL). [GraphQL::Schema.from_definition](rdoc-ref:GraphQL::Schema.from_definition) returns a schema class based on a filename or string containing GraphQL SDL. For example: ```ruby # From a file: @@ -78,7 +70,7 @@ The hash may contain: ## Plugins -{{ "GraphQL::Schema.from_definition" | api_doc }} accepts a `using:` argument, which may be given as a map of `plugin => args` pairs. For example: +[GraphQL::Schema.from_definition](rdoc-ref:GraphQL::Schema.from_definition) 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: { @@ -104,4 +96,4 @@ 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. +See [GraphQL::Language::Nodes::Directive](rdoc-ref:GraphQL::Language::Nodes::Directive) for available methods. diff --git a/guides/subscriptions/ably_implementation.md b/guides/subscriptions/ably_implementation.md index faeeea0e153..9abcdc4f6e5 100644 --- a/guides/subscriptions/ably_implementation.md +++ b/guides/subscriptions/ably_implementation.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Subscriptions -title: Ably Implementation -desc: GraphQL subscriptions over Ably -index: 7 -pro: true ---- +# Ably Implementation [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. @@ -130,7 +121,7 @@ For better performance reading and writing to Redis, you can pass a `connection_ ### Broadcasts -If you set up {% internal_link "Broadcasts", "/subscriptions/broadcast" %}, then you can update many clients over a single Ably channel. +If you set up [Broadcasts](/subscriptions/broadcast), then you can update many clients over a single Ably channel. Broadcast channels have stable, predictable IDs. To prevent unauthorized clients from "listening in," use [token authorization](#authorization) for transport. Broadcasts channels use the namespace `gqlbdcst:`, so you can provide capabilities to receive them using `"gqlbdcst:*" => [ ... ]` in your authorization code. (If you're using [encryption](#encryption), the prefix will be `ablyencr-gqlbdcst:` instead.) @@ -266,7 +257,7 @@ __Backwards compatibility:__ `GraphQL::Pro::AblySubscriptions` will only encrypt 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)`: +By default, this is done with `GraphQL::Subscriptions::Serialize`'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 @@ -296,17 +287,17 @@ That gives you fine-grained control of context reloading. ## Dashboard -You can monitor subscription state in the {% internal_link "GraphQL-Pro Dashboard", "/pro/dashboard" %}: +You can monitor subscription state in the [GraphQL-Pro Dashboard](/pro/dashboard): -{{ "/subscriptions/redis_dashboard_1.png" | link_to_img:"Redis Subscription Dashboard" }} +![Redis Subscription Dashboard](/subscriptions/redis_dashboard_1.png) -{{ "/subscriptions/redis_dashboard_2.png" | link_to_img:"Redis Subscription Detail" }} +![Redis Subscription Detail](/subscriptions/redis_dashboard_2.png) ## 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: +At any time, you can reset your subscription database with the __"Reset"__ button in the [GraphQL-Pro Dashboard](/pro/dashboard), or in Ruby: ```ruby # Wipe all subscription data from the DB: @@ -321,6 +312,6 @@ To receive webhooks in development, you can [use ngrok](https://www.ably.io/tuto Install the [Ably JS client](https://github.com/ably/ably-js) then see docs for: -- {% internal_link "Apollo Client", "/javascript_client/apollo_subscriptions" %} -- {% internal_link "Relay Modern", "/javascript_client/relay_subscriptions" %}. -- {% internal_link "GraphiQL", "/javascript_client/graphiql_subscriptions" %} +- [Apollo Client](/javascript_client/apollo_subscriptions) +- [Relay Modern](/javascript_client/relay_subscriptions). +- [GraphiQL](/javascript_client/graphiql_subscriptions) diff --git a/guides/subscriptions/action_cable_implementation.md b/guides/subscriptions/action_cable_implementation.md index 6969c45c4bc..c1d00543089 100644 --- a/guides/subscriptions/action_cable_implementation.md +++ b/guides/subscriptions/action_cable_implementation.md @@ -1,19 +1,11 @@ ---- -layout: guide -doc_stub: false -search: true -section: Subscriptions -title: Action Cable Implementation -desc: GraphQL subscriptions over ActionCable -index: 4 ---- +# Action Cable Implementation [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 }}. GraphQL-Ruby also includes a mock ActionCable implementation for testing: {{ "GraphQL::Testing::MockActionCable" | api_doc }}. +To get started, see examples in the API docs: [GraphQL::Subscriptions::ActionCableSubscriptions](rdoc-ref:GraphQL::Subscriptions::ActionCableSubscriptions). GraphQL-Ruby also includes a mock ActionCable implementation for testing: [GraphQL::Testing::MockActionCable](rdoc-ref:GraphQL::Testing::MockActionCable). See client usage for: -- {% internal_link "Apollo Client", "/javascript_client/apollo_subscriptions" %} -- {% internal_link "Relay Modern", "/javascript_client/relay_subscriptions" %}. -- {% internal_link "GraphiQL", "/javascript_client/graphiql_subscriptions" %} +- [Apollo Client](/javascript_client/apollo_subscriptions) +- [Relay Modern](/javascript_client/relay_subscriptions). +- [GraphiQL](/javascript_client/graphiql_subscriptions) diff --git a/guides/subscriptions/broadcast.md b/guides/subscriptions/broadcast.md index 9614fe8e590..5e486bfda2d 100644 --- a/guides/subscriptions/broadcast.md +++ b/guides/subscriptions/broadcast.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Subscriptions -title: Broadcasts -desc: Delivering the same GraphQL result to multiple subscribers -index: 3 ---- +# Broadcasts GraphQL subscription updates may _broadcast_ data to multiple subscribers. @@ -79,13 +71,13 @@ GraphQL-Ruby determines which subscribers can receive a broadcast by inspecting: - __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! +So, take care to [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.) +(See [GraphQL::Subscriptions::Event#fingerprint](rdoc-ref:GraphQL::Subscriptions::Event#fingerprint) for the implementation of broadcast fingerprints.) ## Checking for Broadcastable -For testing purposes, you can confirm that a GraphQL query string is broadcastable by using {{ "Subscriptions#broadcastable?" | api_doc }}: +For testing purposes, you can confirm that a GraphQL query string is broadcastable by using [Subscriptions#broadcastable?](rdoc-ref:GraphQL::Subscriptions#broadcastable?): ```ruby subscription_string = "subscription { ... }" diff --git a/guides/subscriptions/implementation.md b/guides/subscriptions/implementation.md index c34336c2aa5..2b0e3a5451b 100644 --- a/guides/subscriptions/implementation.md +++ b/guides/subscriptions/implementation.md @@ -1,18 +1,10 @@ ---- -layout: guide -doc_stub: false -search: true -section: Subscriptions -title: Implementation -desc: Subscription execution and delivery -index: 3 ---- +# Implementation -The {{ "GraphQL::Subscriptions" | api_doc }} plugin is a base class for implementing subscriptions. +The [GraphQL::Subscriptions](rdoc-ref:GraphQL::Subscriptions) 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 }}. +Each method corresponds to a step in the subscription lifecycle. See the API docs for method-by-method documentation: [GraphQL::Subscriptions](rdoc-ref:GraphQL::Subscriptions). -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. +Also, see the [Pusher implementation guide](/subscriptions/pusher_implementation), the [Ably implementation guide](/subscriptions/ably_implementation), the [ActionCable implementation guide](/subscriptions/action_cable_implementation) or [GraphQL::Subscriptions::ActionCableSubscriptions](rdoc-ref:GraphQL::Subscriptions::ActionCableSubscriptions) docs for an example implementation. ## Considerations @@ -25,4 +17,4 @@ Every Ruby application is different, so consider these points when implementing ## 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" %}. +_Broadcasting_ updates to multiple subscribers is supported by GraphQL-Ruby, but requires implementation-specific work, see more in the [Broadcast guide](/subscriptions/broadcast). diff --git a/guides/subscriptions/multi_tenant.md b/guides/subscriptions/multi_tenant.md index 55ccde8b405..3ad3b42c613 100644 --- a/guides/subscriptions/multi_tenant.md +++ b/guides/subscriptions/multi_tenant.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Subscriptions -title: Multi-Tenant -desc: Switching tenants in GraphQL Subscription execution -index: 8 ---- +# Multi-Tenant In a multi-tenant system, data from many different accounts is stored on the same server. (An account might be an organization, a customer, a namespace, a domain, etc -- these are all _tenants_.) Gems like [Apartment](https://github.com/influitive/apartment) assist with this arrangement, but it can also be implemented in the application. Here are a few considerations for this architecture when using GraphQL subscriptions. @@ -26,7 +18,7 @@ MySchema.execute(query_str, context: context, ...) ## Tenant-based `subscription_scope` -When subscriptions are delivered, {% internal_link "`subscription_scope`", "subscriptions/subscription_classes#scope" %} is one element used to route data to the right subscriber. In short, it's the _implicit_ identifier for the receiver. In a multi-tenant architecture, `subscription_scope` should reference the context key that names the tenant, for example: +When subscriptions are delivered, [`subscription_scope`](/subscriptions/subscription_classes#scope) is one element used to route data to the right subscriber. In short, it's the _implicit_ identifier for the receiver. In a multi-tenant architecture, `subscription_scope` should reference the context key that names the tenant, for example: ```ruby class BudgetWasApproved < GraphQL::Schema::Subscription @@ -62,7 +54,7 @@ There are a few places where subscriptions might need to load data: Each of these operations will need to select the right tenant in order to load data properly. -For __building the payload__, use a {% internal_link "Trace module", "queries/tracing" %}: +For __building the payload__, use a [Trace module](/queries/tracing): ```ruby module TenantSelectionTrace diff --git a/guides/subscriptions/overview.md b/guides/subscriptions/overview.md index 29a1241f27f..75765771d69 100644 --- a/guides/subscriptions/overview.md +++ b/guides/subscriptions/overview.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Subscriptions -title: Overview -desc: Introduction to Subscriptions in GraphQL-Ruby -index: 0 ---- +# Overview _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: @@ -20,19 +12,19 @@ _Subscriptions_ allow GraphQL clients to observe specific events and receive upd `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" %}. +Read more in the [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. +[GraphQL::Schema::Subscription](rdoc-ref:GraphQL::Schema::Subscription) 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" %} +Read more in the [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" %}. +Read more in the [Triggers guide](/subscriptions/triggers). ## Implementation @@ -42,12 +34,12 @@ Besides the GraphQL component, your application must provide some subscription-r - __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" %}. +Read more in the [Implementation guide](/subscriptions/implementation) or check out the [ActionCable implementation](/subscriptions/action_cable_implementation), [Pusher implementation](/subscriptions/pusher_implementation) or [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" %}. +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 [Broadcast guide](/subscriptions/broadcast). ## Multi-Tenant -See the {% internal_link "Multi-tenant guide", "subscriptions/multi_tenant" %} for supporting multi-tenancy in GraphQL subscriptions. +See the [Multi-tenant guide](/subscriptions/multi_tenant) for supporting multi-tenancy in GraphQL subscriptions. diff --git a/guides/subscriptions/pusher_implementation.md b/guides/subscriptions/pusher_implementation.md index 6813ffa8b84..163bf09287d 100644 --- a/guides/subscriptions/pusher_implementation.md +++ b/guides/subscriptions/pusher_implementation.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Subscriptions -title: Pusher Implementation -desc: GraphQL subscriptions over Pusher -index: 6 -pro: true ---- +# Pusher Implementation [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. @@ -120,7 +111,7 @@ For better performance reading and writing to Redis, you can pass a `connection_ ### Broadcasts -If you set up {% internal_link "Broadcasts", "/subscriptions/broadcast" %}, then you can update many clients over a single Pusher channel. +If you set up [Broadcasts](/subscriptions/broadcast), then you can update many clients over a single Pusher channel. Broadcast channels have stable, predictable IDs. To prevent unauthorized clients from "listening in," use an [authorized Pusher channel](#authorization) for transport. In your authorization code, you can check for a broadcast using `.broadcast_subscription_id?`: @@ -177,7 +168,7 @@ def execute end ``` -This will cause subscription payloads to include `compressed_result: "..."` instead of `result: "..."` when they're sent over Pusher. See docs for {% internal_link "Apollo Client", "/javascript_client/apollo_subscriptions" %} or {% internal_link "Relay Modern", "/javascript_client/relay_subscriptions" %} to read about preparing clients for compressed payloads. +This will cause subscription payloads to include `compressed_result: "..."` instead of `result: "..."` when they're sent over Pusher. See docs for [Apollo Client](/javascript_client/apollo_subscriptions) or [Relay Modern](/javascript_client/relay_subscriptions) to read about preparing clients for compressed payloads. By configuring `compress_pusher_payload: true` on a query-by-query basis, the subscription backend can continue to support clients running _old_ client code (by not compressing) while upgrading new clients to compressed payloads. @@ -197,7 +188,7 @@ Your server needs to receive webhooks from Pusher when clients disconnect. This In the Pusher web UI, Add a webhook for "Channel existence" -{{ "/subscriptions/pusher_webhook_configuration.png" | link_to_img:"Pusher Webhook Configuration" }} +![Pusher Webhook Configuration](/subscriptions/pusher_webhook_configuration.png) Then, mount the Rack app for handling webhooks from Pusher. For example, on Rails: @@ -258,7 +249,7 @@ end 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)`: +By default, this is done with `GraphQL::Subscriptions::Serialize`'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 @@ -288,17 +279,17 @@ That gives you fine-grained control of context reloading. ## Dashboard -You can monitor subscription state in the {% internal_link "GraphQL-Pro Dashboard", "/pro/dashboard" %}: +You can monitor subscription state in the [GraphQL-Pro Dashboard](/pro/dashboard): -{{ "/subscriptions/redis_dashboard_1.png" | link_to_img:"Redis Subscription Dashboard" }} +![Redis Subscription Dashboard](/subscriptions/redis_dashboard_1.png) -{{ "/subscriptions/redis_dashboard_2.png" | link_to_img:"Redis Subscription Detail" }} +![Redis Subscription Detail](/subscriptions/redis_dashboard_2.png) ## 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: +At any time, you can reset your subscription database with the __"Reset"__ button in the [GraphQL-Pro Dashboard](/pro/dashboard), or in Ruby: ```ruby # Wipe all subscription data from the DB: @@ -313,7 +304,7 @@ To receive Pusher's webhooks in development, Pusher [suggests using ngrok](https Install the [Pusher JS client](https://github.com/pusher/pusher-js) then see docs for: -- {% internal_link "Apollo Client", "/javascript_client/apollo_subscriptions" %} -- {% internal_link "Relay Modern", "/javascript_client/relay_subscriptions" %} -- {% internal_link "GraphiQL", "/javascript_client/graphiql_subscriptions" %} -- {% internal_link "urql", "/javascript_client/urql_subscriptions" %} +- [Apollo Client](/javascript_client/apollo_subscriptions) +- [Relay Modern](/javascript_client/relay_subscriptions) +- [GraphiQL](/javascript_client/graphiql_subscriptions) +- [urql](/javascript_client/urql_subscriptions) diff --git a/guides/subscriptions/subscription_classes.md b/guides/subscriptions/subscription_classes.md index af0914dd719..1b65cc8d43f 100644 --- a/guides/subscriptions/subscription_classes.md +++ b/guides/subscriptions/subscription_classes.md @@ -1,14 +1,6 @@ ---- -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. +# Subscription Classes + +You can extend [GraphQL::Schema::Subscription](rdoc-ref:GraphQL::Schema::Subscription) to create fields that can be subscribed to. These classes support several behaviors: @@ -34,7 +26,7 @@ class Subscriptions::BaseSubscription < GraphQL::Schema::Subscription 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 }}.) +(This base class is a lot like the [mutation base class](/mutations/mutation_classes). They're both subclasses of [GraphQL::Schema::Resolver](rdoc-ref:GraphQL::Schema::Resolver).) ## Extend the base class and hook it up @@ -46,7 +38,7 @@ 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: +Then, hook up the new class to the [Subscription root type](/subscriptions/subscription_type) with the `subscription:` option: ```ruby class Types::SubscriptionType < Types::BaseObject @@ -66,7 +58,7 @@ subscription { ## 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: +Subscription fields take [arguments](/fields/arguments) just like normal fields. They also accept a [`loads:` option](/mutations/mutation_classes#auto-loading-arguments) just like mutations. For example: ```ruby class Subscriptions::MessageWasPosted < Subscriptions::BaseSubscription @@ -148,7 +140,7 @@ payload_type Types::MessageType ## 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. +Usually, GraphQL-Ruby uses explicitly-passed arguments to determine when a [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. @@ -191,7 +183,7 @@ MyAppSchema.subscriptions.trigger( ) ``` -Scope is also used for determining whether subscribers can receive the same {% internal_link "broadcast", "subscriptions/implementation#broadcast" %}. +Scope is also used for determining whether subscribers can receive the same [broadcast](/subscriptions/implementation#broadcasts). ## Check Permissions with #authorized? @@ -270,7 +262,7 @@ subscription($roomId: ID!) { ## Subsequent Updates with #update -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: +After a client has registered a subscription, the application may trigger subscription updates with `MySchema.subscriptions.trigger(...)` (see the [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. @@ -368,4 +360,4 @@ class Subscriptions::JobFinished < GraphQL::Schema::Subscription end ``` -See the {% internal_link "Extra Field Metadata", "/fields/introduction#extra-field-metadata" %} for more information about available metadata. +See the [Extra Field Metadata](/fields/introduction#extra-field-metadata) for more information about available metadata. diff --git a/guides/subscriptions/subscription_type.md b/guides/subscriptions/subscription_type.md index e8b1c5290e9..b3d61791b8e 100644 --- a/guides/subscriptions/subscription_type.md +++ b/guides/subscriptions/subscription_type.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Subscriptions -title: Subscription Type -desc: The root type for subscriptions -index: 1 ---- +# Subscription Type `Subscription` is the entry point for all subscriptions in a GraphQL system. Each field corresponds to an event which may be subscribed to: @@ -57,6 +49,6 @@ class MySchema < GraphQL::Schema end ``` -See {% internal_link "Implementing Subscriptions","subscriptions/implementation" %} for more about actually delivering updates. +See [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. +See [Subscription Classes](/subscriptions/subscription_classes) for more about implementing subscription root fields. diff --git a/guides/subscriptions/triggers.md b/guides/subscriptions/triggers.md index c55cf7e5a53..58287f70390 100644 --- a/guides/subscriptions/triggers.md +++ b/guides/subscriptions/triggers.md @@ -1,16 +1,8 @@ ---- -layout: guide -doc_stub: false -search: true -section: Subscriptions -title: Triggers -desc: Sending updates from your application to GraphQL -index: 2 ---- +# Triggers 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" %} +Events are triggered _by name_, and the name must match fields on your [Subscription Type](/subscriptions/subscription_type) ```ruby # Update the system with the new blog post: @@ -28,7 +20,7 @@ The arguments are: 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 }}) +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`.) To specify that a topic is scoped, add a `subscription_scope` option to the Subscription class: @@ -42,7 +34,7 @@ class Subscriptions::CommentAdded < Subscription::BaseSubscription end ``` -(Read more in the {% internal_link "Subscription Classes guide", "subscriptions/subscription_classes#scope" %}.) +(Read more in the [Subscription Classes guide](/subscriptions/subscription_classes#scope).) Then, subscription operations should have a `context: { current_user_id: ... }` value, for example: diff --git a/guides/testing/helpers.md b/guides/testing/helpers.md index f7e248f8ba2..99bec0f6019 100644 --- a/guides/testing/helpers.md +++ b/guides/testing/helpers.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Testing -title: Helpers -desc: Running GraphQL fields in isolation -index: 3 ---- +# Helpers GraphQL-Ruby ships with a test helper method, `run_graphql_field`, that can execute a GraphQL field in isolation. To use it in your test suite, include the module with your schema class: @@ -15,7 +7,7 @@ GraphQL-Ruby ships with a test helper method, `run_graphql_field`, that can exec include GraphQL::Testing::Helpers.for(MySchema) ``` -Then, you can run fields using {{ "Testing::Helpers#run_graphql_field" | api_doc }}: +Then, you can run fields using [Testing::Helpers#run_graphql_field](rdoc-ref:GraphQL::Testing::Helpers#run_graphql_field): ```ruby post = Post.first @@ -37,16 +29,16 @@ Additionally, it accepts some keyword arguments: - Checks `.visible?` on the named Object Type, raising an error if it isn't visible - Wraps the given runtime object in the GraphQL Object Type -- Checks `.authorized?` on the type, calling {{ "Schema.unauthorized_object" | api_doc }} if authorization fails +- Checks `.authorized?` on the type, calling [Schema.unauthorized_object](rdoc-ref:GraphQL::Schema.unauthorized_object) if authorization fails - Prepares arguments for field resolution - Checks `#visible?` on the field, raising an error if the field isn't visible -- Checks `#authorized?` on the field, calling {{ "Schema.unauthorized_field" | api_doc }} if it fails -- Calls any {% internal_link "field extensions", "/type_definitions/field_extensions" %} -- Runs {% internal_link "Dataloader", "/dataloader/overview" %} and/or GraphQL-Batch, as needed +- Checks `#authorized?` on the field, calling [Schema.unauthorized_field](rdoc-ref:GraphQL::Schema.unauthorized_field) if it fails +- Calls any [field extensions](/type_definitions/field_extensions) +- Runs [Dataloader](/dataloader/overview) and/or GraphQL-Batch, as needed ## Resolving fields on the same object -You can use {{ "Testing::Helpers#with_resolution_context" | api_doc }} to use the same type, runtime object, and GraphQL context for multiple field resolutions. For example: +You can use [Testing::Helpers#with_resolution_context](rdoc-ref:GraphQL::Testing::Helpers#with_resolution_context) to use the same type, runtime object, and GraphQL context for multiple field resolutions. For example: ```ruby # Assuming `include GraphQL::Testing::Helpers.for(MySchema)` diff --git a/guides/testing/integration_tests.md b/guides/testing/integration_tests.md index d21b6acac80..6b668581456 100644 --- a/guides/testing/integration_tests.md +++ b/guides/testing/integration_tests.md @@ -1,14 +1,6 @@ ---- -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: +# Integration Tests + +Besides testing [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. diff --git a/guides/testing/overview.md b/guides/testing/overview.md index 6c12f827be0..99f2402ddba 100644 --- a/guides/testing/overview.md +++ b/guides/testing/overview.md @@ -1,18 +1,7 @@ ---- -layout: guide -doc_stub: false -search: true -section: Testing -title: Overview -desc: Testing a GraphQL system -index: 0 -redirect_from: - - /schema/testing ---- - +# Overview 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 "Integration testing", "/testing/integration_tests" %} exercises the various behaviors of the GraphQL system, making sure that it returns the right data to the right clients. -- {% internal_link "Testing helpers", "/testing/helpers" %} for running GraphQL fields without writing a whole query +- [Structure testing](/testing/schema_structure) verifies that schema changes are backwards-compatible. This way, you don't break existing clients. +- [Integration testing](/testing/integration_tests) exercises the various behaviors of the GraphQL system, making sure that it returns the right data to the right clients. +- [Testing helpers](/testing/helpers) for running GraphQL fields without writing a whole query diff --git a/guides/testing/profiling.md b/guides/testing/profiling.md index 68c577a05f3..3b175798880 100644 --- a/guides/testing/profiling.md +++ b/guides/testing/profiling.md @@ -1,16 +1,8 @@ ---- -layout: guide -doc_stub: false -search: true -section: Testing -title: Profiling -desc: Profiling the performance of GraphQL-Ruby -index: 4 ---- +# Profiling If you want to know more about how time is spent during GraphQL queries, including GraphQL-Ruby internals, you can use Ruby profiling tools to take a closer look. -If you want to investigate GraphQL-Ruby performance together, prepare a runtime profile and memory profile as described below and {% open_an_issue "Performance investigation" %} on GitHub, including those files. +If you want to investigate GraphQL-Ruby performance together, prepare a runtime profile and memory profile as described below and [open an issue](https://github.com/rmosolgo/graphql-ruby/issues/new?title=Performance+investigation&body=) on GitHub, including those files. ## StackProf diff --git a/guides/testing/schema_structure.md b/guides/testing/schema_structure.md index e2e1e85fc45..9e9de9de5f7 100644 --- a/guides/testing/schema_structure.md +++ b/guides/testing/schema_structure.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Testing -title: Schema Structure -desc: Make sure that your schema changes are backwards-compatible -index: 1 ---- +# Schema Structure Structural changes to a GraphQL schema come in two categories: @@ -21,7 +13,7 @@ Here are few tips for managing schema structure changes. 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/ruby/graphql/2017/03/16/tracking-schema-changes-with-graphql-ruby) or the built-in {{ "GraphQL::RakeTask" | api_doc }} for generating schema dumps. +You can read about this approach in ["Tracking Schema Changes with GraphQL-Ruby"](https://rmosolgo.github.io/ruby/graphql/2017/03/16/tracking-schema-changes-with-graphql-ruby) or the built-in [GraphQL::RakeTask](rdoc-ref:GraphQL::RakeTask) for generating schema dumps. ## Automatically check for breaking changes diff --git a/guides/type_definitions/directives.md b/guides/type_definitions/directives.md index 21182f0c454..85ef9bc6445 100644 --- a/guides/type_definitions/directives.md +++ b/guides/type_definitions/directives.md @@ -1,13 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Type Definitions -title: Directives -desc: Special instructions for the GraphQL runtime -index: 10 ---- - +# Directives Directives are system-defined keywords with two kinds of uses: @@ -33,12 +24,12 @@ query ProfileView($renderingDetailedProfile: Boolean!){ 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 }}) +- `@skip(if: ...)` skips the selection if the `if: ...` value is truthy ([GraphQL::Schema::Directive::Skip](rdoc-ref:GraphQL::Schema::Directive::Skip)) +- `@include(if: ...)` includes the selection if the `if: ...` value is truthy ([GraphQL::Schema::Directive::Include](rdoc-ref:GraphQL::Schema::Directive::Include)) ### Custom Runtime Directives -Custom directives extend {{ "GraphQL::Schema::Directive" | api_doc }}: +Custom directives extend [GraphQL::Schema::Directive](rdoc-ref:GraphQL::Schema::Directive): ```ruby # app/graphql/directives/my_directive.rb @@ -67,7 +58,7 @@ query { } ``` -{{ "GraphQL::Schema::Directive::Feature" | api_doc }} and {{ "GraphQL::Schema::Directive::Transform" | api_doc }} are included in the library as examples. +[GraphQL::Schema::Directive::Feature](rdoc-ref:GraphQL::Schema::Directive::Feature) and [GraphQL::Schema::Directive::Transform](rdoc-ref:GraphQL::Schema::Directive::Transform) are included in the library as examples. ### Runtime hooks @@ -76,7 +67,7 @@ Directive classes may implement the following class methods to interact with the - `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. -Looking for a runtime hook that isn't listed here? {% open_an_issue "New directive hook: @something", " " %} to start the conversation! +Looking for a runtime hook that isn't listed here? [open an issue](https://github.com/rmosolgo/graphql-ruby/issues/new?title=New+directive+hook%3A+%40something&body=%3C%21--+Describe+how+the+directive+would+be+used+and+then+how+you+might+implement+it+--%3E+) to start the conversation! ## Schema Directives @@ -94,7 +85,7 @@ In the schema definition, directives express metadata about types, fields, and a ### Custom Schema Directives -To make a custom schema directive, extend {{ "GraphQL::Schema::Directive" | api_doc }}: +To make a custom schema directive, extend [GraphQL::Schema::Directive](rdoc-ref:GraphQL::Schema::Directive): ```ruby # app/graphql/directives/permission.rb @@ -122,14 +113,14 @@ field :salary, Integer, null: false, After that: - the configured object's `.directives` method will return an array containing an instance of the specified directive -- IDL dumps (from {{ "Schema.to_definition" | api_doc }}) will include the configured directives +- IDL dumps (from [Schema.to_definition](rdoc-ref:GraphQL::Schema.to_definition)) will include the configured directives -Similarly, {{ "Schema.from_definition" | api_doc }} parses directives from IDL strings. +Similarly, [Schema.from_definition](rdoc-ref:GraphQL::Schema.from_definition) parses directives from IDL strings. For a couple of built-in examples, check out: -- {{ "GraphQL::Schema::Directive::Deprecated" | api_doc }} which implements `deprecation_reason` (via {{ "GraphQL::Schema::Member::HasDeprecationReason" | api_doc}}) -- {{ "GraphQL::Schema::Directive::Flagged" | api_doc }}, which is an example of using schema directives to implement {% internal_link "visibility", "/authorization/visibility" %} +- [GraphQL::Schema::Directive::Deprecated](rdoc-ref:GraphQL::Schema::Directive::Deprecated) which implements `deprecation_reason` (via [GraphQL::Schema::Member::HasDeprecationReason](rdoc-ref:GraphQL::Schema::Member::HasDeprecationReason)) +- [GraphQL::Schema::Directive::Flagged](rdoc-ref:GraphQL::Schema::Directive::Flagged), which is an example of using schema directives to implement [visibility](/authorization/visibility) ## Custom Name @@ -143,7 +134,7 @@ end ## Arguments -Like fields, directives may have {% internal_link "arguments", "/fields/arguments" %} : +Like fields, directives may have [arguments](/fields/arguments) : ```ruby argument :if, Boolean, diff --git a/guides/type_definitions/enums.md b/guides/type_definitions/enums.md index 7575b811062..42cefa78032 100644 --- a/guides/type_definitions/enums.md +++ b/guides/type_definitions/enums.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Type Definitions -title: Enums -desc: Enums are sets of discrete values -index: 2 ---- +# Enums 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/#enum-types) (SDL), enums are described like this: @@ -39,7 +31,7 @@ params["variables"] ## Defining Enum Types -In your application, enums extend {{ "GraphQL::Schema::Enum" | api_doc }} and define values with the `value(...)` method: +In your application, enums extend [GraphQL::Schema::Enum](rdoc-ref:GraphQL::Schema::Enum) and define values with the `value(...)` method: ```ruby # First, a base class diff --git a/guides/type_definitions/extensions.md b/guides/type_definitions/extensions.md index 214570d734b..fc9d30bd3ef 100644 --- a/guides/type_definitions/extensions.md +++ b/guides/type_definitions/extensions.md @@ -1,14 +1,4 @@ ---- -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/ ---- +# Extending the GraphQL-Ruby Type Definition System While integrating GraphQL into your app, you can customize the definition DSL. For example, you might: diff --git a/guides/type_definitions/field_extensions.md b/guides/type_definitions/field_extensions.md index 45a06faba74..972a1cc01a3 100644 --- a/guides/type_definitions/field_extensions.md +++ b/guides/type_definitions/field_extensions.md @@ -1,18 +1,10 @@ ---- -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 }}). +# Field Extensions + +[GraphQL::Schema::FieldExtension](rdoc-ref:GraphQL::Schema::FieldExtension) 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](rdoc-ref:GraphQL::Schema::Field::ConnectionExtension)). ## Making a new extension -Field extensions are subclasses of {{ "GraphQL::Schema::FieldExtension" | api_doc }}: +Field extensions are subclasses of [GraphQL::Schema::FieldExtension](rdoc-ref:GraphQL::Schema::FieldExtension): ```ruby class MyExtension < GraphQL::Schema::FieldExtension @@ -50,7 +42,7 @@ This way, an extension can encapsulate a behavior requiring several configuratio ## Adding default argument configurations -Extensions may provide _default_ argument configurations which are applied if the field doesn't define the argument for itself. The configuration is passed to {{ "Schema::FieldExtension.default_argument" | api_doc }}. For example, to define a `:query` argument if the field doesn't already have one: +Extensions may provide _default_ argument configurations which are applied if the field doesn't define the argument for itself. The configuration is passed to [Schema::FieldExtension.default_argument](rdoc-ref:GraphQL::Schema::FieldExtension.default_argument). For example, to define a `:query` argument if the field doesn't already have one: ```ruby class SearchableExtension < GraphQL::Schema::FieldExtension @@ -66,9 +58,9 @@ Additionally, extensions may implement `def after_define` which is called _after 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. +First, [GraphQL::Schema::FieldExtension#resolve](rdoc-ref:GraphQL::Schema::FieldExtension#resolve) 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. +After resolution and _after_ syncing lazy values (like `Promise`s from `graphql-batch`), [GraphQL::Schema::FieldExtension#after_resolve](rdoc-ref:GraphQL::Schema::FieldExtension#after_resolve) 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. @@ -124,7 +116,7 @@ field :name, String, null: false, extensions: [LimitExtension => { limit: 20 }] ## Using `extras` -Extensions can have the same `extras` as fields (see {% internal_link "Extra Field Metadata", "fields/introduction#extra-field-metadata" %}). Add them by calling `extras` in the class definition: +Extensions can have the same `extras` as fields (see [Extra Field Metadata](/fields/introduction#extra-field-metadata)). Add them by calling `extras` in the class definition: ```ruby class MyExtension < GraphQL::Schema::FieldExtension @@ -136,7 +128,7 @@ Any configured `extras` will be present in the given `arguments`, but removed be ## Adding an extension by default -If you want to apply an extension to _all_ your fields, you can do this in your {% internal_link "BaseField", "/type_definitions/extensions.html#customizing-fields" %}'s `def initialize`, for example: +If you want to apply an extension to _all_ your fields, you can do this in your [BaseField](/type_definitions/extensions.html#customizing-fields)'s `def initialize`, for example: ```ruby class Types::BaseField < GraphQL::Schema::Field diff --git a/guides/type_definitions/input_objects.md b/guides/type_definitions/input_objects.md index 3cfadd00775..afb4e9bd9c5 100644 --- a/guides/type_definitions/input_objects.md +++ b/guides/type_definitions/input_objects.md @@ -1,12 +1,4 @@ ---- -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 Objects 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: @@ -36,7 +28,7 @@ This input object has three possible keys: ## Defining Input Object Types -Input object types extend {{ "GraphQL::Schema::InputObject" | api_doc }} and define key-value pairs with the `argument(...)` method. For example: +Input object types extend [GraphQL::Schema::InputObject](rdoc-ref:GraphQL::Schema::InputObject) and define key-value pairs with the `argument(...)` method. For example: ```ruby # app/graphql/types/base_input_object.rb @@ -52,7 +44,7 @@ class Types::PostAttributes < Types::BaseInputObject end ``` -For a full description of the `argument(...)` method, see the {% internal_link "argument section of the Objects guide","/fields/arguments.html" %}. +For a full description of the `argument(...)` method, see the [argument section of the Objects guide](/fields/arguments.html). ## Using Input Objects @@ -101,8 +93,8 @@ 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::Execution::Interpreter::Arguments" | api_doc }} instance -- `@context`: The current {{ "GraphQL::Query::Context" | api_doc }} +- `@arguments`: A [GraphQL::Execution::Interpreter::Arguments](rdoc-ref:GraphQL::Execution::Interpreter::Arguments) instance +- `@context`: The current [GraphQL::Query::Context](rdoc-ref:GraphQL::Query::Context) Any extra methods you define on the class can be used for field resolution, as demonstrated above. diff --git a/guides/type_definitions/interfaces.md b/guides/type_definitions/interfaces.md index 84738ff4ede..59a38b5966e 100644 --- a/guides/type_definitions/interfaces.md +++ b/guides/type_definitions/interfaces.md @@ -1,14 +1,4 @@ ---- -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 Interfaces are lists of fields which may be implemented by object types. @@ -63,11 +53,11 @@ Whether the objects are `Company` or `Individual`, it doesn't matter -- you stil 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. +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 [Union](/type_definitions/unions) instead. ## Defining Interface Types -Interfaces are Ruby modules which include {{ "GraphQL::Schema::Interface" | api_doc }}. First, make a base module: +Interfaces are Ruby modules which include [GraphQL::Schema::Interface](rdoc-ref:GraphQL::Schema::Interface). First, make a base module: ```ruby module Types::BaseInterface @@ -139,7 +129,7 @@ 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" %}. +Read more in the [Fields guide](/fields/introduction). ### Definition Methods diff --git a/guides/type_definitions/lists.md b/guides/type_definitions/lists.md index aff9a4db2b2..b84cd6299a2 100644 --- a/guides/type_definitions/lists.md +++ b/guides/type_definitions/lists.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Type Definitions -title: Lists -desc: Ordered lists containing other types -index: 6 ---- +# Lists 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/#list) (SDL). diff --git a/guides/type_definitions/non_nulls.md b/guides/type_definitions/non_nulls.md index 330d5aa8e7e..ee5ddb2a470 100644 --- a/guides/type_definitions/non_nulls.md +++ b/guides/type_definitions/non_nulls.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Type Definitions -title: Non-Null Types -desc: Values which must be present -index: 7 ---- +# Non-Null Types GraphQL's concept of _non-null_ is expressed in the [Schema Definition Language](https://graphql.org/learn/schema/#non-null) (SDL) with `!`, for example: diff --git a/guides/type_definitions/objects.md b/guides/type_definitions/objects.md index 1718c2810b4..2de420f6853 100644 --- a/guides/type_definitions/objects.md +++ b/guides/type_definitions/objects.md @@ -1,12 +1,4 @@ ---- -layout: guide -doc_stub: false -search: true -section: Type Definitions -title: Objects -desc: Objects expose data and link to other objects -index: 0 ---- +# Objects 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: @@ -58,7 +50,7 @@ The rest of this guide will describe how to define GraphQL object types in Ruby. ## 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. +Classes extending [GraphQL::Schema::Object](rdoc-ref:GraphQL::Schema::Object) 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) @@ -91,7 +83,7 @@ end 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. +See the [Fields guide](/fields/introduction) for details about object fields. ## Implementing interfaces @@ -108,4 +100,4 @@ 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" %} +Read more about interfaces in the [Interfaces guide](/type_definitions/interfaces) diff --git a/guides/type_definitions/scalars.md b/guides/type_definitions/scalars.md index b5d15647106..708c5e40dbf 100644 --- a/guides/type_definitions/scalars.md +++ b/guides/type_definitions/scalars.md @@ -1,14 +1,6 @@ ---- -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: +# Scalars + +Scalars are "leaf" values in GraphQL. There are several built-in scalars, and you can define custom scalars, too. ([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 @@ -17,7 +9,7 @@ Scalars are "leaf" values in GraphQL. There are several built-in scalars, and yo - `ID`, which a specialized `String` for representing unique object identifiers - `ISO8601DateTime`, an ISO 8601-encoded datetime - `ISO8601Date`, an ISO 8601-encoded date -- `ISO8601Duration`, an ISO 8601-encoded duration. ⚠ This requires `ActiveSupport::Duration` to be loaded and will raise {{ "GraphQL::Error" | api_doc }} if it's `.coerce_*` methods are called when it is not defined. +- `ISO8601Duration`, an ISO 8601-encoded duration. ⚠ This requires `ActiveSupport::Duration` to be loaded and will raise [GraphQL::Error](rdoc-ref:GraphQL::Error) if it's `.coerce_*` methods are called when it is not defined. - `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 @@ -63,7 +55,7 @@ scalar DateTime ## Custom Scalars -You can implement your own scalars by extending {{ "GraphQL::Schema::Scalar" | api_doc }}. For example: +You can implement your own scalars by extending [GraphQL::Schema::Scalar](rdoc-ref:GraphQL::Schema::Scalar). For example: ```ruby # app/graphql/types/base_scalar.rb @@ -99,6 +91,6 @@ 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. +When incoming data is incorrect, the method may raise [GraphQL::CoercionError](rdoc-ref:GraphQL::CoercionError), which will be returned to the client in the `"errors"` key. Scalar classes are never initialized; only their `.coerce_*` methods are called at runtime. diff --git a/guides/type_definitions/unions.md b/guides/type_definitions/unions.md index 4b99f55f6c4..62bc8280e42 100644 --- a/guides/type_definitions/unions.md +++ b/guides/type_definitions/unions.md @@ -1,12 +1,4 @@ ---- -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 ---- +# Unions 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/#union-types) (SDL): @@ -36,7 +28,7 @@ searchMedia(term: "puppies") { 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. +[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). @@ -73,4 +65,4 @@ The `possible_types(*types)` method accepts one or more types which belong to th 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" %}. +For information about `.resolve_type`, see the [Interfaces guide](/type_definitions/interfaces#resolve-type). diff --git a/lib/generators/graphql/loader_generator.rb b/lib/generators/graphql/loader_generator.rb index d953f005d7c..7699ee6603d 100644 --- a/lib/generators/graphql/loader_generator.rb +++ b/lib/generators/graphql/loader_generator.rb @@ -5,8 +5,13 @@ module Graphql module Generators - # @example Generate a `GraphQL::Batch` loader by name. - # rails g graphql:loader RecordLoader + # **Examples** + # + # **Example: Generate a `GraphQL::Batch` loader by name.** + # + # ```ruby + # rails g graphql:loader RecordLoader + # ``` class LoaderGenerator < Rails::Generators::NamedBase include Core diff --git a/lib/generators/graphql/mutation_create_generator.rb b/lib/generators/graphql/mutation_create_generator.rb index 049cb7284d2..07d251939b5 100644 --- a/lib/generators/graphql/mutation_create_generator.rb +++ b/lib/generators/graphql/mutation_create_generator.rb @@ -5,8 +5,13 @@ module Graphql module Generators # TODO: What other options should be supported? # - # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name - # rails g graphql:mutation CreatePostMutation + # **Examples** + # + # **Example: Generate a `GraphQL::Schema::RelayClassicMutation` by name** + # + # ```ruby + # rails g graphql:mutation CreatePostMutation + # ``` class MutationCreateGenerator < OrmMutationsBase desc "Scaffold a Relay Classic ORM create mutation for the given model class" diff --git a/lib/generators/graphql/mutation_delete_generator.rb b/lib/generators/graphql/mutation_delete_generator.rb index bf33a7606b6..56412f2f3fc 100644 --- a/lib/generators/graphql/mutation_delete_generator.rb +++ b/lib/generators/graphql/mutation_delete_generator.rb @@ -5,8 +5,13 @@ module Graphql module Generators # TODO: What other options should be supported? # - # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name - # rails g graphql:mutation DeletePostMutation + # **Examples** + # + # **Example: Generate a `GraphQL::Schema::RelayClassicMutation` by name** + # + # ```ruby + # rails g graphql:mutation DeletePostMutation + # ``` class MutationDeleteGenerator < OrmMutationsBase desc "Scaffold a Relay Classic ORM delete mutation for the given model class" diff --git a/lib/generators/graphql/mutation_generator.rb b/lib/generators/graphql/mutation_generator.rb index 5eebd2adaa1..b33834daac5 100644 --- a/lib/generators/graphql/mutation_generator.rb +++ b/lib/generators/graphql/mutation_generator.rb @@ -7,8 +7,13 @@ module Graphql module Generators # TODO: What other options should be supported? # - # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name - # rails g graphql:mutation CreatePostMutation + # **Examples** + # + # **Example: Generate a `GraphQL::Schema::RelayClassicMutation` by name** + # + # ```ruby + # rails g graphql:mutation CreatePostMutation + # ``` class MutationGenerator < Rails::Generators::NamedBase include Core diff --git a/lib/generators/graphql/mutation_update_generator.rb b/lib/generators/graphql/mutation_update_generator.rb index 6200dcc570a..67193255f09 100644 --- a/lib/generators/graphql/mutation_update_generator.rb +++ b/lib/generators/graphql/mutation_update_generator.rb @@ -5,8 +5,13 @@ module Graphql module Generators # TODO: What other options should be supported? # - # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name - # rails g graphql:mutation UpdatePostMutation + # **Examples** + # + # **Example: Generate a `GraphQL::Schema::RelayClassicMutation` by name** + # + # ```ruby + # rails g graphql:mutation UpdatePostMutation + # ``` class MutationUpdateGenerator < OrmMutationsBase desc "Scaffold a Relay Classic ORM update mutation for the given model class" diff --git a/lib/generators/graphql/orm_mutations_base.rb b/lib/generators/graphql/orm_mutations_base.rb index 74a35d163fb..87a6ed64397 100644 --- a/lib/generators/graphql/orm_mutations_base.rb +++ b/lib/generators/graphql/orm_mutations_base.rb @@ -7,8 +7,13 @@ module Graphql module Generators # TODO: What other options should be supported? # - # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name - # rails g graphql:mutation CreatePostMutation + # **Examples** + # + # **Example: Generate a `GraphQL::Schema::RelayClassicMutation` by name** + # + # ```ruby + # rails g graphql:mutation CreatePostMutation + # ``` class OrmMutationsBase < Rails::Generators::NamedBase include Core include Rails::Generators::ResourceHelpers diff --git a/lib/generators/graphql/type_generator.rb b/lib/generators/graphql/type_generator.rb index 55f207c532f..b08200ac511 100644 --- a/lib/generators/graphql/type_generator.rb +++ b/lib/generators/graphql/type_generator.rb @@ -34,10 +34,19 @@ def create_type_file # 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 + # + # **Parameters** + # + # - `type_expresson` (`String`) + # - `mode` (`Symbol`) + # - `null` (`Boolean`) + # + # **Returns** + # + # - `(String, Boolean)` — The type expression, followed by `null:` value + # + # :call-seq: + # normalize_type_expression(type_expression, Symbol mode:, bool null:) -> (String, bool) 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) @@ -73,22 +82,42 @@ def self.normalize_type_expression(type_expression, mode:, null: true) private - # @return [String] The user-provided type name, normalized to Ruby code + # **Returns** + # + # - `String` — The user-provided type name, normalized to Ruby code + # + # :call-seq: + # type_ruby_name() -> String def type_ruby_name @type_ruby_name ||= self.class.normalize_type_expression(name, mode: :ruby)[0] end - # @return [String] The user-provided type name, as a GraphQL name + # **Returns** + # + # - `String` — The user-provided type name, as a GraphQL name + # + # :call-seq: + # type_graphql_name() -> String def type_graphql_name @type_graphql_name ||= self.class.normalize_type_expression(name, mode: :graphql)[0] end - # @return [String] The user-provided type name, as a file name (without extension) + # **Returns** + # + # - `String` — The user-provided type name, as a file name (without extension) + # + # :call-seq: + # type_file_name() -> String def type_file_name @type_file_name ||= "#{type_graphql_name}Type".underscore end - # @return [Array] User-provided fields, in `(name, Ruby type name)` pairs + # **Returns** + # + # - `Array` — User-provided fields, in `(name, Ruby type name)` pairs + # + # :call-seq: + # normalized_fields() -> Array[NormalizedField] def normalized_fields @normalized_fields ||= fields.map { |f| name, raw_type = f.split(":", 2) diff --git a/lib/graphql.rb b/lib/graphql.rb index 110a40a54d7..73928e6e8f6 100644 --- a/lib/graphql.rb +++ b/lib/graphql.rb @@ -11,6 +11,9 @@ module GraphQL extend Autoload # Load all `autoload`-configured classes, and also eager-load dependents who have autoloads of their own. + # + # This is useful during application boot when autoloading is disabled or + # when a framework needs to eager-load its namespaces. def self.eager_load! super Query.eager_load! @@ -36,6 +39,14 @@ class RequiredImplementationMissingError < Error end class << self + # Get the parser used by `GraphQL.parse` and `GraphQL.parse_file`. + # + # **Returns** + # + # - `Class` — the configured parser class + # + # :call-seq: + # default_parser() -> Class def default_parser @default_parser ||= GraphQL::Language::Parser end @@ -44,21 +55,44 @@ def 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] + # + # **Parameters** + # + # - `graphql_string` (`String`) — a GraphQL query string or schema definition + # + # **Returns** + # + # - `GraphQL::Language::Nodes::Document` + # + # :call-seq: + # parse(String graphql_string, trace:, filename:, max_tokens:) -> GraphQL::Language::Nodes::Document def self.parse(graphql_string, trace: GraphQL::Tracing::NullTrace, filename: nil, max_tokens: nil) default_parser.parse(graphql_string, trace: trace, filename: filename, max_tokens: max_tokens) 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] + # + # **Parameters** + # + # - `filename` (`String`) — Path to a `.graphql` file containing IDL or query + # + # **Returns** + # + # - `GraphQL::Language::Nodes::Document` + # + # :call-seq: + # parse_file(String filename) -> GraphQL::Language::Nodes::Document def self.parse_file(filename) content = File.read(filename) default_parser.parse(content, filename: filename) end - # @return [Array] + # **Returns** + # + # - `Array` + # + # :call-seq: + # scan(graphql_string) -> Array[Array] def self.scan(graphql_string) default_parser.scan(graphql_string) end diff --git a/lib/graphql/analysis.rb b/lib/graphql/analysis.rb index 36f14019f8f..2e1d6f95b6d 100644 --- a/lib/graphql/analysis.rb +++ b/lib/graphql/analysis.rb @@ -21,9 +21,17 @@ def initialize(...) # 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 + # **Parameters** + # + # - `multiplex` (`GraphQL::Execution::Multiplex`) + # - `analyzers` (`Array`) + # + # **Returns** + # + # - `Array` — Results from multiplex analyzers + # + # :call-seq: + # analyze_multiplex(GraphQL::Execution::Multiplex multiplex, Array[GraphQL::Analysis::Analyzer] analyzers) -> Array[Any] def analyze_multiplex(multiplex, analyzers) multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) } @@ -50,9 +58,17 @@ def analyze_multiplex(multiplex, analyzers) end end - # @param query [GraphQL::Query] - # @param analyzers [Array] - # @return [Array] Results from those analyzers + # **Parameters** + # + # - `query` (`GraphQL::Query`) + # - `analyzers` (`Array`) + # + # **Returns** + # + # - `Array` — Results from those analyzers + # + # :call-seq: + # analyze_query(GraphQL::Query query, Array[GraphQL::Analysis::Analyzer] analyzers, multiplex_analyzers:) -> Array[Any] def analyze_query(query, analyzers, multiplex_analyzers: []) query.current_trace.analyze_query(query: query) do query_analyzers = analyzers.map { |analyzer| analyzer.new(query) } diff --git a/lib/graphql/analysis/analyzer.rb b/lib/graphql/analysis/analyzer.rb index ab9943d51d4..10ff3a18606 100644 --- a/lib/graphql/analysis/analyzer.rb +++ b/lib/graphql/analysis/analyzer.rb @@ -7,7 +7,9 @@ module Analysis # 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 + # **Parameters** + # + # - `The` (`GraphQL::Query, GraphQL::Execution::Multiplex`) — query or multiplex to analyze class Analyzer def initialize(subject) @subject = subject @@ -23,21 +25,39 @@ def initialize(subject) # 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 + # + # **Returns** + # + # - `Boolean` — If the query should be analyzed or not + # + # :call-seq: + # analyze?() -> bool def analyze? true end # Analyzer hook to decide at analysis time whether analysis # requires a visitor pass; can be disabled for precomputed results. - # @return [Boolean] If analysis requires visitation or not + # + # **Returns** + # + # - `Boolean` — If analysis requires visitation or not + # + # :call-seq: + # visit?() -> bool def visit? true end - # The result for this analyzer. Returning {GraphQL::AnalysisError} results + # The result for this analyzer. Returning [GraphQL::AnalysisError](rdoc-ref:GraphQL::AnalysisError) results # in a query error. - # @return [Any] The analyzer result + # + # **Returns** + # + # - `Any` — The analyzer result + # + # :call-seq: + # result() -> Any def result raise GraphQL::RequiredImplementationMissingError end @@ -76,14 +96,28 @@ def on_leave_#{member_name}(node, parent, visitor) # rubocop:enable Development/NoEvalCop protected - # @return [GraphQL::Query, GraphQL::Execution::Multiplex] Whatever this analyzer is analyzing + # **Returns** + # + # - `GraphQL::Query, GraphQL::Execution::Multiplex` — Whatever this analyzer is analyzing + # + # :call-seq: + # subject -> GraphQL::Query | GraphQL::Execution::Multiplex 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) + # **Returns** + # + # - `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) + # + # :call-seq: + # query -> GraphQL::Query | nil attr_reader :query - # @return [GraphQL::Execution::Multiplex, nil] `nil` if this analyzer is visiting a query + # **Returns** + # + # - `GraphQL::Execution::Multiplex, nil` — `nil` if this analyzer is visiting a query + # + # :call-seq: + # multiplex -> GraphQL::Execution::Multiplex | nil attr_reader :multiplex end end diff --git a/lib/graphql/analysis/query_complexity.rb b/lib/graphql/analysis/query_complexity.rb index 9afd19f728e..b0b09e56071 100644 --- a/lib/graphql/analysis/query_complexity.rb +++ b/lib/graphql/analysis/query_complexity.rb @@ -51,11 +51,19 @@ class ScopedTypeComplexity < Hash attr_reader :field_definition, :response_path, :query - # @param parent_type [Class] The owner of `field_definition` - # @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 - # @return [Hash>] + # **Parameters** + # + # - `parent_type` (`Class`) — The owner of `field_definition` + # - `field_definition` (`GraphQL::Field, GraphQL::Schema::Field`) — Used for getting the `.complexity` configuration + # - `query` (`GraphQL::Query`) — Used for `query.possible_types` + # - `response_path` (`Array`) — The path to the response key for the field + # + # **Returns** + # + # - `Hash>` + # + # :call-seq: + # initialize(Class parent_type, GraphQL::Field | GraphQL::Schema::Field field_definition, GraphQL::Query query, Array[String] response_path) -> Hash[GraphQL::BaseType, Hash[String, ScopedTypeComplexity]] def initialize(parent_type, field_definition, query, response_path) super(&DEFAULT_PROC) @parent_type = parent_type @@ -65,7 +73,12 @@ def initialize(parent_type, field_definition, query, response_path) @nodes = [] end - # @return [Array] + # **Returns** + # + # - `Array` + # + # :call-seq: + # nodes -> Array[GraphQL::Language::Nodes::Field] attr_reader :nodes def own_complexity(child_complexity) @@ -107,17 +120,30 @@ def on_leave_field(node, parent, visitor) private - # @return [Integer] + # **Returns** + # + # - `Integer` + # + # :call-seq: + # max_possible_complexity(mode:) -> Integer def max_possible_complexity(mode: :future) @complexities_on_type_by_query.reduce(0) do |total, (query, scopes_stack)| total + merged_max_complexity_for_scopes(query, [scopes_stack.first], mode) end end - # @param query [GraphQL::Query] Used for `query.possible_types` - # @param scopes [Array] Array of scoped type complexities - # @param mode [:future, :legacy] - # @return [Integer] + # **Parameters** + # + # - `query` (`GraphQL::Query`) — Used for `query.possible_types` + # - `scopes` (`Array`) — Array of scoped type complexities + # - `mode` (`:future, :legacy`) + # + # **Returns** + # + # - `Integer` + # + # :call-seq: + # merged_max_complexity_for_scopes(GraphQL::Query query, Array[ScopedTypeComplexity] scopes, :future | :legacy mode) -> Integer def merged_max_complexity_for_scopes(query, scopes, mode) # Aggregate a set of all possible scope types encountered (scope keys). # Use a hash, but ignore the values; it's just a fast way to work with the keys. @@ -182,14 +208,27 @@ def types_intersect?(query, a, b) # 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 + # **Parameters** + # + # - `scoped_type_complexity` (`ScopedTypeComplexity`) + # - `max_complexity` (`Numeric`) — Field's maximum complexity including child complexity + # - `child_complexity` (`Numeric, nil`) — Field's child complexity + # + # :call-seq: + # field_complexity(ScopedTypeComplexity scoped_type_complexity, Numeric max_complexity:, Numeric | nil child_complexity:) def field_complexity(scoped_type_complexity, max_complexity:, child_complexity: nil) end - # @param inner_selections [Array>] Field selections for a scope - # @return [Integer] Total complexity value for all these selections in the parent scope + # **Parameters** + # + # - `inner_selections` (`Array>`) — Field selections for a scope + # + # **Returns** + # + # - `Integer` — Total complexity value for all these selections in the parent scope + # + # :call-seq: + # merged_max_complexity(query, Array[Hash[String, ScopedTypeComplexity]] inner_selections) -> Integer def merged_max_complexity(query, inner_selections) child_scopes_by_key = {} inner_selections.each do |inner_selection| diff --git a/lib/graphql/analysis/query_depth.rb b/lib/graphql/analysis/query_depth.rb index b6859bb119e..31c1817e078 100644 --- a/lib/graphql/analysis/query_depth.rb +++ b/lib/graphql/analysis/query_depth.rb @@ -5,24 +5,28 @@ module Analysis # # 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 + # **Examples** # - # # In your Schema file: + # **Example: Logging the depth of a query** # - # class MySchema < GraphQL::Schema - # query_analyzer LogQueryDepth + # ```ruby + # class LogQueryDepth < GraphQL::Analysis::QueryDepth + # def result + # log("GraphQL query depth: #{@max_depth}") # end + # end + # + # # In your Schema file: # - # # When you run the query, the depth will get logged: + # class MySchema < GraphQL::Schema + # query_analyzer LogQueryDepth + # end # - # Schema.execute(query_str) - # # GraphQL query depth: 8 + # # When you run the query, the depth will get logged: # + # Schema.execute(query_str) + # # GraphQL query depth: 8 + # ``` class QueryDepth < Analyzer def initialize(query) @max_depth = 0 diff --git a/lib/graphql/analysis/visitor.rb b/lib/graphql/analysis/visitor.rb index 4e1d96bcdc2..7ab89374054 100644 --- a/lib/graphql/analysis/visitor.rb +++ b/lib/graphql/analysis/visitor.rb @@ -8,7 +8,7 @@ module Analysis # only the selected operation, providing helpers for common use cases such # as skipped fields and visiting fragment spreads. # - # @see {GraphQL::Analysis::Analyzer} AST Analyzers for queries + # See [GraphQL::Analysis::Analyzer](rdoc-ref:GraphQL::Analysis::Analyzer) AST Analyzers for queries class Visitor < GraphQL::Language::StaticVisitor def initialize(query:, analyzers:, timeout:) @analyzers = analyzers @@ -32,13 +32,28 @@ def initialize(query:, analyzers:, timeout:) super(query.selected_operation) end - # @return [GraphQL::Query] the query being visited + # **Returns** + # + # - `GraphQL::Query` — the query being visited + # + # :call-seq: + # query -> GraphQL::Query attr_reader :query - # @return [Array] Types whose scope we've entered + # **Returns** + # + # - `Array` — Types whose scope we've entered + # + # :call-seq: + # object_types -> Array[GraphQL::ObjectType] attr_reader :object_types - # @return [Array` — Array of errors rescued during analysis + # + # :call-seq: + # rescued_errors -> Array[GraphQL::AnalysisError] attr_reader :rescued_errors def visit @@ -48,23 +63,44 @@ def visit # Visit Helpers - # @return [GraphQL::Execution::Interpreter::Arguments] Arguments for this node, merging default values, literal values and query variables - # @see {GraphQL::Query#arguments_for} + # See [GraphQL::Query#arguments_for](rdoc-ref:GraphQL::Query#arguments_for) + # + # **Returns** + # + # - `GraphQL::Execution::Interpreter::Arguments` — Arguments for this node, merging default values, literal values and query variables + # + # :call-seq: + # arguments_for(ast_node, field_definition) -> GraphQL::Execution::Interpreter::Arguments 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 + # **Returns** + # + # - `Boolean` — If the visitor is currently inside a fragment definition + # + # :call-seq: + # visiting_fragment_definition?() -> bool def visiting_fragment_definition? @in_fragment_def end - # @return [Boolean] If the current node should be skipped because of a skip or include directive + # **Returns** + # + # - `Boolean` — If the current node should be skipped because of a skip or include directive + # + # :call-seq: + # skipping?() -> bool def skipping? @skipping end - # @return [Array] The path to the response key for the current field + # **Returns** + # + # - `Array` — The path to the response key for the current field + # + # :call-seq: + # response_path() -> Array[String] def response_path @response_path.dup end @@ -206,37 +242,72 @@ def on_fragment_spread(node, parent) @path.pop end - # @return [GraphQL::BaseType] The current object type + # **Returns** + # + # - `GraphQL::BaseType` — The current object type + # + # :call-seq: + # type_definition() -> GraphQL::BaseType def type_definition @object_types.last end - # @return [GraphQL::BaseType] The type which the current type came from + # **Returns** + # + # - `GraphQL::BaseType` — The type which the current type came from + # + # :call-seq: + # parent_type_definition() -> GraphQL::BaseType def parent_type_definition @object_types[-2] end - # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one + # **Returns** + # + # - `GraphQL::Field, nil` — The most-recently-entered GraphQL::Field, if currently inside one + # + # :call-seq: + # field_definition() -> GraphQL::Field | nil def field_definition @field_definitions.last end - # @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to + # **Returns** + # + # - `GraphQL::Field, nil` — The GraphQL field which returned the object that the current field belongs to + # + # :call-seq: + # previous_field_definition() -> GraphQL::Field | nil def previous_field_definition @field_definitions[-2] end - # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one + # **Returns** + # + # - `GraphQL::Directive, nil` — The most-recently-entered GraphQL::Directive, if currently inside one + # + # :call-seq: + # directive_definition() -> GraphQL::Directive | nil def directive_definition @directive_definitions.last end - # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one + # **Returns** + # + # - `GraphQL::Argument, nil` — The most-recently-entered GraphQL::Argument, if currently inside one + # + # :call-seq: + # argument_definition() -> GraphQL::Argument | nil def argument_definition @argument_definitions.last end - # @return [GraphQL::Argument, nil] The previous GraphQL argument + # **Returns** + # + # - `GraphQL::Argument, nil` — The previous GraphQL argument + # + # :call-seq: + # previous_argument_definition() -> GraphQL::Argument | nil def previous_argument_definition @argument_definitions[-2] end diff --git a/lib/graphql/autoload.rb b/lib/graphql/autoload.rb index 82aa538cfc4..4ba81a72026 100644 --- a/lib/graphql/autoload.rb +++ b/lib/graphql/autoload.rb @@ -1,13 +1,22 @@ # frozen_string_literal: true module GraphQL - # @see GraphQL::Railtie for automatic Rails integration + # See [GraphQL::Railtie](rdoc-ref:GraphQL::Railtie) for automatic Rails integration module Autoload # Register a constant named `const_name` to be loaded from `path`. - # This is like `Kernel#autoload` but it tracks the constants so they can be eager-loaded with {#eager_load!} - # @param const_name [Symbol] - # @param path [String] - # @return [void] + # This is like `Kernel#autoload` but it tracks the constants so they can be eager-loaded with [eager load!](rdoc-ref:#eager_load!) + # + # **Parameters** + # + # - `const_name` (`Symbol`) + # - `path` (`String`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # autoload(Symbol const_name, String path) -> void def autoload(const_name, path) @_eagerloaded_constants ||= [] @_eagerloaded_constants << const_name @@ -16,7 +25,13 @@ def autoload(const_name, path) end # Call this to load this constant's `autoload` dependents and continue calling recursively - # @return [void] + # + # **Returns** + # + # - `void` + # + # :call-seq: + # eager_load!() -> void def eager_load! @_eager_loading = true if @_eagerloaded_constants @@ -30,7 +45,12 @@ def eager_load! private - # @return [Boolean] `true` if GraphQL-Ruby is currently eager-loading its constants + # **Returns** + # + # - `Boolean` — `true` if GraphQL-Ruby is currently eager-loading its constants + # + # :call-seq: + # eager_loading?() -> bool def eager_loading? @_eager_loading ||= false end diff --git a/lib/graphql/backtrace.rb b/lib/graphql/backtrace.rb index c97543d1fe0..17a55b152d8 100644 --- a/lib/graphql/backtrace.rb +++ b/lib/graphql/backtrace.rb @@ -2,18 +2,22 @@ require "graphql/backtrace/table" require "graphql/backtrace/traced_error" module GraphQL - # Wrap unhandled errors with {TracedError}. + # Wrap unhandled errors with [TracedError](rdoc-ref:TracedError). # - # {TracedError} provides a GraphQL backtrace with arguments and return values. - # The underlying error is available as {TracedError#cause}. + # [TracedError](rdoc-ref:TracedError) provides a GraphQL backtrace with arguments and return values. + # The underlying error is available as `TracedError#cause`. # - # @example toggling backtrace annotation - # class MySchema < GraphQL::Schema - # if Rails.env.development? || Rails.env.test? - # use GraphQL::Backtrace - # end - # end + # **Examples** + # + # **Example: toggling backtrace annotation** # + # ```ruby + # class MySchema < GraphQL::Schema + # if Rails.env.development? || Rails.env.test? + # use GraphQL::Backtrace + # end + # end + # ``` class Backtrace include Enumerable extend Forwardable diff --git a/lib/graphql/backtrace/table.rb b/lib/graphql/backtrace/table.rb index 04a60769d59..2890342b449 100644 --- a/lib/graphql/backtrace/table.rb +++ b/lib/graphql/backtrace/table.rb @@ -18,12 +18,22 @@ def initialize(context, value:) @override_value = value end - # @return [String] A table layout of backtrace with metadata + # **Returns** + # + # - `String` — A table layout of backtrace with metadata + # + # :call-seq: + # to_table() -> String def to_table @to_table ||= render_table(rows) end - # @return [Array] An array of position + field name entries + # **Returns** + # + # - `Array` — An array of position + field name entries + # + # :call-seq: + # to_backtrace() -> Array[String] def to_backtrace @to_backtrace ||= begin backtrace = rows.map { |r| "#{r[0]}: #{r[1]}" } @@ -134,7 +144,12 @@ def find_ast_node(node, last_part) nil end - # @return [String] + # **Returns** + # + # - `String` + # + # :call-seq: + # render_table(rows) -> String def render_table(rows) max = Array.new(HEADERS.length, MIN_COL_WIDTH) diff --git a/lib/graphql/backtrace/traced_error.rb b/lib/graphql/backtrace/traced_error.rb index caa7c7d7311..0bfc2a935f7 100644 --- a/lib/graphql/backtrace/traced_error.rb +++ b/lib/graphql/backtrace/traced_error.rb @@ -3,10 +3,20 @@ 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 + # **Returns** + # + # - `Array` — Printable backtrace of GraphQL error context + # + # :call-seq: + # graphql_backtrace -> Array[String] attr_reader :graphql_backtrace - # @return [GraphQL::Query::Context] The context at the field where the error was raised + # **Returns** + # + # - `GraphQL::Query::Context` — The context at the field where the error was raised + # + # :call-seq: + # context -> GraphQL::Query::Context attr_reader :context MESSAGE_TEMPLATE = <<-MESSAGE diff --git a/lib/graphql/current.rb b/lib/graphql/current.rb index 5d5bbe08636..fafb5a71e1f 100644 --- a/lib/graphql/current.rb +++ b/lib/graphql/current.rb @@ -5,24 +5,32 @@ module GraphQL # # It won't work across unrelated fibers, although it will work in child Fibers. # - # @example Setting Up ActiveRecord::QueryLogs + # **Examples** # - # config.active_record.query_log_tags = [ - # :namespaced_controller, - # :action, - # :job, - # # ... - # { - # # GraphQL runtime info: - # current_graphql_operation: -> { GraphQL::Current.operation_name }, - # current_graphql_field: -> { GraphQL::Current.field&.path }, - # current_dataloader_source: -> { GraphQL::Current.dataloader_source_class }, - # # ... - # }, - # ] + # **Example: Setting Up ActiveRecord::QueryLogs** # + # ```ruby + # config.active_record.query_log_tags = [ + # :namespaced_controller, + # :action, + # :job, + # # ... + # { + # # GraphQL runtime info: + # current_graphql_operation: -> { GraphQL::Current.operation_name }, + # current_graphql_field: -> { GraphQL::Current.field&.path }, + # current_dataloader_source: -> { GraphQL::Current.dataloader_source_class }, + # # ... + # }, + # ] + # ``` module Current - # @return [String, nil] Comma-joined operation names for the currently-running {Execution::Multiplex}. `nil` if all operations are anonymous. + # **Returns** + # + # - `String, nil` — Comma-joined operation names for the currently-running `Execution::Multiplex`. `nil` if all operations are anonymous. + # + # :call-seq: + # operation_name() -> String | nil def self.operation_name if (m = Fiber[:__graphql_current_multiplex]) m.context[:__graphql_current_operation_name] ||= begin @@ -38,8 +46,14 @@ def self.operation_name end end - # @see GraphQL::Field#path for a string identifying this field - # @return [GraphQL::Field, nil] The currently-running field, if there is one. + # See [Schema::Member::HasPath#path](rdoc-ref:GraphQL::Schema::Member::HasPath#path) for a string identifying this field + # + # **Returns** + # + # - `GraphQL::Field, nil` — The currently-running field, if there is one. + # + # :call-seq: + # field() -> GraphQL::Field | nil def self.field if (interpreter_info = Fiber[:__graphql_runtime_info]) interpreter_info.values&.first&.current_field @@ -50,12 +64,22 @@ def self.field end end - # @return [Class, nil] The currently-running {Dataloader::Source} class, if there is one. + # **Returns** + # + # - `Class, nil` — The currently-running [Dataloader::Source](rdoc-ref:Dataloader::Source) class, if there is one. + # + # :call-seq: + # dataloader_source_class() -> Class | nil def self.dataloader_source_class Fiber[:__graphql_current_dataloader_source]&.class end - # @return [GraphQL::Dataloader::Source, nil] The currently-running source, if there is one + # **Returns** + # + # - `GraphQL::Dataloader::Source, nil` — The currently-running source, if there is one + # + # :call-seq: + # dataloader_source() -> GraphQL::Dataloader::Source | nil def self.dataloader_source Fiber[:__graphql_current_dataloader_source] end diff --git a/lib/graphql/dashboard.rb b/lib/graphql/dashboard.rb index 05a26657ee9..cfa1d0e5a8e 100644 --- a/lib/graphql/dashboard.rb +++ b/lib/graphql/dashboard.rb @@ -5,36 +5,49 @@ module Graphql # `GraphQL::Dashboard` is a `Rails::Engine`-based dashboard for viewing metadata about your GraphQL schema. # # Pass the class name of your schema when mounting it. - # @see GraphQL::Tracing::DetailedTrace DetailedTrace for viewing production traces in the Dashboard + # See [GraphQL::Tracing::DetailedTrace](rdoc-ref:GraphQL::Tracing::DetailedTrace) DetailedTrace for viewing production traces in the Dashboard # - # @example Mounting the Dashboard in your app - # mount GraphQL::Dashboard, at: "graphql_dashboard", schema: "MySchema" + # **Examples** # - # Pass an array to allow selecting from multiple schemas with the `schema` query parameter. - # mount GraphQL::Dashboard, at: "graphql_dashboard", schema: ["MySchema", "OtherSchema"] + # **Example: Mounting the Dashboard in your app** # - # @example Authenticating the Dashboard with HTTP Basic Auth - # # config/initializers/graphql_dashboard.rb - # GraphQL::Dashboard.middleware.use(Rack::Auth::Basic) do |username, password| - # # Compare the provided username/password to an application setting: - # ActiveSupport::SecurityUtils.secure_compare(Rails.application.credentials.graphql_dashboard_username, username) && - # ActiveSupport::SecurityUtils.secure_compare(Rails.application.credentials.graphql_dashboard_username, password) - # end + # ```ruby + # mount GraphQL::Dashboard, at: "graphql_dashboard", schema: "MySchema" + # ``` + # + # To allow selecting from multiple schemas with the `schema` query parameter: + # + # ```ruby + # mount GraphQL::Dashboard, at: "graphql_dashboard", schema: ["MySchema", "OtherSchema"] + # ``` # - # @example Custom Rails authentication - # # config/initializers/graphql_dashboard.rb - # ActiveSupport.on_load(:graphql_dashboard_application_controller) do - # # context here is GraphQL::Dashboard::ApplicationController + # **Example: Authenticating the Dashboard with HTTP Basic Auth** # - # before_action do - # raise ActionController::RoutingError.new('Not Found') unless current_user&.admin? - # end + # ```ruby + # # config/initializers/graphql_dashboard.rb + # GraphQL::Dashboard.middleware.use(Rack::Auth::Basic) do |username, password| + # # Compare the provided username/password to an application setting: + # ActiveSupport::SecurityUtils.secure_compare(Rails.application.credentials.graphql_dashboard_username, username) && + # ActiveSupport::SecurityUtils.secure_compare(Rails.application.credentials.graphql_dashboard_username, password) + # end + # ``` # - # def current_user - # # load current user - # end + # **Example: Custom Rails authentication** + # + # ```ruby + # # config/initializers/graphql_dashboard.rb + # ActiveSupport.on_load(:graphql_dashboard_application_controller) do + # # context here is GraphQL::Dashboard::ApplicationController + # + # before_action do + # raise ActionController::RoutingError.new('Not Found') unless current_user&.admin? # end # + # def current_user + # # load current user + # end + # end + # ``` class Dashboard < Rails::Engine engine_name "graphql_dashboard" isolate_namespace(Graphql::Dashboard) diff --git a/lib/graphql/dataloader.rb b/lib/graphql/dataloader.rb index f58209995b9..df31696d838 100644 --- a/lib/graphql/dataloader.rb +++ b/lib/graphql/dataloader.rb @@ -8,22 +8,27 @@ require "graphql/dataloader/active_record_source" module GraphQL - # This plugin supports Fiber-based concurrency, along with {GraphQL::Dataloader::Source}. + # This plugin supports Fiber-based concurrency, along with [GraphQL::Dataloader::Source](rdoc-ref:GraphQL::Dataloader::Source). # - # @example Installing Dataloader + # **Examples** # - # class MySchema < GraphQL::Schema - # use GraphQL::Dataloader - # end + # **Example: Installing Dataloader** # - # @example Waiting for batch-loaded data in a GraphQL field + # ```ruby + # class MySchema < GraphQL::Schema + # use GraphQL::Dataloader + # end + # ``` # - # field :team, Types::Team, null: true + # **Example: Waiting for batch-loaded data in a GraphQL field** # - # def team - # dataloader.with(Sources::Record, Team).load(object.team_id) - # end + # ```ruby + # field :team, Types::Team, null: true # + # def team + # dataloader.with(Sources::Record, Team).load(object.team_id) + # end + # ``` class Dataloader class << self attr_accessor :default_nonblocking, :default_fiber_limit @@ -69,7 +74,12 @@ def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.cl @lazies_at_depth = Hash.new { |h, k| h[k] = [] } end - # @return [Integer, nil] + # **Returns** + # + # - `Integer, nil` + # + # :call-seq: + # fiber_limit -> Integer | nil attr_reader :fiber_limit def nonblocking? @@ -79,7 +89,12 @@ def nonblocking? # This is called before the fiber is spawned, from the parent context (i.e. from # the thread or fiber that it is scheduled from). # - # @return [Hash] Current fiber-local variables + # **Returns** + # + # - `Hash` — Current fiber-local variables + # + # :call-seq: + # get_fiber_variables() -> Hash[Symbol, Object] def get_fiber_variables fiber_vars = {} Thread.current.keys.each do |fiber_var_key| @@ -92,8 +107,16 @@ def get_fiber_variables # # This is called within the fiber, right after it is spawned. # - # @param vars [Hash] Fiber-local variables from {get_fiber_variables} - # @return [void] + # **Parameters** + # + # - `vars` (`Hash`) — Fiber-local variables from [get_fiber_variables](rdoc-ref:get_fiber_variables) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # set_fiber_variables(Hash[Symbol, Object] vars) -> void def set_fiber_variables(vars) vars.each { |k, v| Thread.current[k] = v } nil @@ -106,10 +129,14 @@ def cleanup_fiber # Get a Source instance from this dataloader, for calling `.load(...)` or `.request(...)` on. # - # @param source_class [Class] - # @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`, - # and cached for the lifetime of this {Multiplex}. + # **Parameters** + # + # - `source_class` (`Class`) — The source class to load + # - `batch_parameters` (`Array`) + # + # **Returns** + # + # - `GraphQL::Dataloader::Source` — An instance of [source_class](rdoc-ref:source_class), initialized with `self, *batch_parameters`, and cached for the lifetime of this [Multiplex](rdoc-ref:Multiplex). if (RUBY_ENGINE == "ruby" && RUBY_VERSION < "3") || RUBY_ENGINE == "truffleruby" # truffle-ruby wasn't doing well with the implementation below def with(source_class, *batch_args) batch_key = source_class.batch_key_for(*batch_args) @@ -133,7 +160,12 @@ def with(source_class, *batch_args, **batch_kwargs) # # Dataloader will resume the fiber after the requested data has been loaded (by another Fiber). # - # @return [void] + # **Returns** + # + # - `void` + # + # :call-seq: + # yield(source:) -> void def yield(source = Fiber[:__graphql_current_dataloader_source]) trace = Fiber[:__graphql_current_multiplex]&.current_trace trace&.dataloader_fiber_yield(source) @@ -142,24 +174,28 @@ def yield(source = Fiber[:__graphql_current_dataloader_source]) nil end - # @api private Nothing to see here - def append_job(callable = nil, &job) + def append_job(callable = nil, &job) # :nodoc: # Given a block, queue it up to be worked through when `#run` is called. # (If the dataloader is already running, then a Fiber will pick this up later.) @pending_jobs.push(callable || job) nil end - # @api private - def queue_pending_source(source) + def queue_pending_source(source) # :nodoc: if @pending_source_set.add?(source) @pending_sources << source end nil end - # Clear any already-loaded objects from {Source} caches - # @return [void] + # Clear any already-loaded objects from [Source](rdoc-ref:Source) caches + # + # **Returns** + # + # - `void` + # + # :call-seq: + # clear_cache() -> void def clear_cache @source_cache.each do |_source_class, batched_sources| batched_sources.each_value(&:clear_cache) @@ -205,7 +241,12 @@ def run_isolated end end - # @param trace_query_lazy [nil, Execution::Multiplex] + # **Parameters** + # + # - `trace_query_lazy` (`nil, Execution::Multiplex`) + # + # :call-seq: + # run(nil | Execution::Multiplex trace_query_lazy:) def run(trace_query_lazy: nil) trace = Fiber[:__graphql_current_multiplex]&.current_trace jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit @@ -255,8 +296,7 @@ def run_fiber(f) f.resume end - # @api private - def lazy_at_depth(depth, lazy) + def lazy_at_depth(depth, lazy) # :nodoc: @lazies_at_depth[depth] << lazy end @@ -270,11 +310,20 @@ def spawn_fiber end # Pre-warm the Dataloader cache with ActiveRecord objects which were loaded elsewhere. - # These will be used by {Dataloader::ActiveRecordSource}, {Dataloader::ActiveRecordAssociationSource} and their helper + # These will be used by [Dataloader::ActiveRecordSource](rdoc-ref:Dataloader::ActiveRecordSource), [Dataloader::ActiveRecordAssociationSource](rdoc-ref:Dataloader::ActiveRecordAssociationSource) and their helper # methods, `dataload_record` and `dataload_association`. - # @param records [Array] Already-loaded records to warm the cache with - # @param index_by [Symbol] The attribute to use as the cache key. (Should match `find_by:` when using {ActiveRecordSource}) - # @return [void] + # + # **Parameters** + # + # - `records` (`Array`) — Already-loaded records to warm the cache with + # - `index_by` (`Symbol`) — The attribute to use as the cache key. (Should match `find_by:` when using [ActiveRecordSource](rdoc-ref:ActiveRecordSource)) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # merge_records(Array[ActiveRecord::Base] records, Symbol index_by:) -> void def merge_records(records, index_by: :id) records_by_class = Hash.new { |h, k| h[k] = {} } records.each do |r| diff --git a/lib/graphql/dataloader/async_dataloader.rb b/lib/graphql/dataloader/async_dataloader.rb index a1ddf0ff3f1..47953db29e5 100644 --- a/lib/graphql/dataloader/async_dataloader.rb +++ b/lib/graphql/dataloader/async_dataloader.rb @@ -23,8 +23,7 @@ def initialize(...) create_pending_run end - # @api private - attr_reader :pending_sources + attr_reader :pending_sources # :nodoc: def create_pending_run jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit diff --git a/lib/graphql/dataloader/request.rb b/lib/graphql/dataloader/request.rb index c66a41fed3c..6911dd1d38b 100644 --- a/lib/graphql/dataloader/request.rb +++ b/lib/graphql/dataloader/request.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module GraphQL class Dataloader - # @see Source#request which returns an instance of this + # See [Source#request](rdoc-ref:Source#request) which returns an instance of this class Request def initialize(source, key) @source = source @@ -10,7 +10,12 @@ def initialize(source, key) # Call this method to cause the current Fiber to wait for the results of this request. # - # @return [Object] the object loaded for `key` + # **Returns** + # + # - `Object` — the object loaded for `key` + # + # :call-seq: + # load() -> Object def load @source.load(@key) end diff --git a/lib/graphql/dataloader/request_all.rb b/lib/graphql/dataloader/request_all.rb index dbcea6558d2..f0d83e00aca 100644 --- a/lib/graphql/dataloader/request_all.rb +++ b/lib/graphql/dataloader/request_all.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module GraphQL class Dataloader - # @see Source#request_all which returns an instance of this. + # See [Source#request_all](rdoc-ref:Source#request_all) which returns an instance of this. class RequestAll < Request def initialize(source, keys) @source = source @@ -10,7 +10,12 @@ def initialize(source, keys) # Call this method to cause the current Fiber to wait for the results of this request. # - # @return [Array] One object for each of `keys` + # **Returns** + # + # - `Array` — One object for each of `keys` + # + # :call-seq: + # load() -> Array[Object] def load @source.load_all(@keys) end diff --git a/lib/graphql/dataloader/source.rb b/lib/graphql/dataloader/source.rb index d3f94903852..9f964fc54a0 100644 --- a/lib/graphql/dataloader/source.rb +++ b/lib/graphql/dataloader/source.rb @@ -3,9 +3,8 @@ module GraphQL class Dataloader class Source - # Called by {Dataloader} to prepare the {Source}'s internal state - # @api private - def setup(dataloader) + # Called by [Dataloader](rdoc-ref:Dataloader) to prepare the [Source](rdoc-ref:Source)'s internal state + def setup(dataloader) # :nodoc: # These keys have been requested but haven't been fetched yet @pending = {} # These keys have been passed to `fetch` but haven't been finished yet @@ -17,7 +16,12 @@ def setup(dataloader) attr_reader :dataloader - # @return [Dataloader::Request] a pending request for a value from `key`. Call `.load` on that object to wait for the result. + # **Returns** + # + # - `Dataloader::Request` — a pending request for a value from `key`. Call `.load` on that object to wait for the result. + # + # :call-seq: + # request(value) -> Dataloader::Request def request(value) res_key = result_key_for(value) add_pending_key(res_key, value) @@ -27,25 +31,46 @@ def request(value) # Implement this method to return a stable identifier if different # key objects should load the same data value. # - # @param value [Object] A value passed to `.request` or `.load`, for which a value will be loaded - # @return [Object] The key for tracking this pending data + # **Parameters** + # + # - `value` (`Object`) — A value passed to `.request` or `.load`, for which a value will be loaded + # + # **Returns** + # + # - `Object` — The key for tracking this pending data + # + # :call-seq: + # result_key_for(Object value) -> Object def result_key_for(value) value end - # Implement this method if varying values given to {load} (etc) should be consolidated - # or normalized before being handed off to your {fetch} implementation. + # Implement this method if varying values given to [load](rdoc-ref:load) (etc) should be consolidated + # or normalized before being handed off to your [fetch](rdoc-ref:fetch) implementation. # - # This is different than {result_key_for} because _that_ method handles unification inside Dataloader's cache, - # but this method changes the value passed into {fetch}. + # This is different than [result_key_for](rdoc-ref:result_key_for) because _that_ method handles unification inside Dataloader's cache, + # but this method changes the value passed into [fetch](rdoc-ref:fetch). # - # @param value [Object] The value passed to {load}, {load_all}, {request}, or {request_all} - # @return [Object] The value given to {fetch} + # **Parameters** + # + # - `value` (`Object`) — The value passed to [load](rdoc-ref:load), [load_all](rdoc-ref:load_all), [request](rdoc-ref:request), or [request_all](rdoc-ref:request_all) + # + # **Returns** + # + # - `Object` — The value given to [fetch](rdoc-ref:fetch) + # + # :call-seq: + # normalize_fetch_key(Object value) -> Object def normalize_fetch_key(value) value end - # @return [Dataloader::Request] a pending request for a values from `keys`. Call `.load` on that object to wait for the results. + # **Returns** + # + # - `Dataloader::Request` — a pending request for a values from `keys`. Call `.load` on that object to wait for the results. + # + # :call-seq: + # request_all(values) -> Dataloader::Request def request_all(values) values.each do |v| res_key = result_key_for(v) @@ -54,8 +79,16 @@ def request_all(values) Dataloader::RequestAll.new(self, values) end - # @param value [Object] A loading value which will be passed to {#fetch} if it isn't already in the internal cache. - # @return [Object] The result from {#fetch} for `key`. If `key` hasn't been loaded yet, the Fiber will yield until it's loaded. + # **Parameters** + # + # - `value` (`Object`) — A loading value which will be passed to [fetch](rdoc-ref:#fetch) if it isn't already in the internal cache. + # + # **Returns** + # + # - `Object` — The result from [fetch](rdoc-ref:#fetch) for `key`. If `key` hasn't been loaded yet, the Fiber will yield until it's loaded. + # + # :call-seq: + # load(Object value) -> Object def load(value) result_key = result_key_for(value) if @results.key?(result_key) @@ -67,8 +100,16 @@ def load(value) end end - # @param values [Array] Loading keys which will be passed to `#fetch` (or read from the internal cache). - # @return [Object] The result from {#fetch} for `keys`. If `keys` haven't been loaded yet, the Fiber will yield until they're loaded. + # **Parameters** + # + # - `values` (`Array`) — Loading keys which will be passed to `#fetch` (or read from the internal cache). + # + # **Returns** + # + # - `Object` — The result from [fetch](rdoc-ref:#fetch) for `keys`. If `keys` haven't been loaded yet, the Fiber will yield until they're loaded. + # + # :call-seq: + # load_all(Array[Object] values) -> Object def load_all(values) result_keys = [] pending_keys = [] @@ -89,8 +130,17 @@ def load_all(values) end # Subclasses must implement this method to return a value for each of `keys` - # @param keys [Array] keys passed to {#load}, {#load_all}, {#request}, or {#request_all} - # @return [Array] A loaded value for each of `keys`. The array must match one-for-one to the list of `keys`. + # + # **Parameters** + # + # - `keys` (`Array`) — keys passed to [load](rdoc-ref:#load), [load all](rdoc-ref:#load_all), [request](rdoc-ref:#request), or [request all](rdoc-ref:#request_all) + # + # **Returns** + # + # - `Array` — A loaded value for each of `keys`. The array must match one-for-one to the list of `keys`. + # + # :call-seq: + # fetch(Array[Object] keys) -> Array[Object] def fetch(keys) # somehow retrieve these from the backend raise "Implement `#{self.class}#fetch(#{keys.inspect}) to return a record for each of the keys" @@ -99,7 +149,13 @@ def fetch(keys) MAX_ITERATIONS = 1000 # Wait for a batch, if there's anything to batch. # Then run the batch and update the cache. - # @return [void] + # + # **Returns** + # + # - `void` + # + # :call-seq: + # sync(pending_result_keys) -> void def sync(pending_result_keys) @dataloader.queue_pending_source(self) if pending? @dataloader.yield(self) @@ -114,15 +170,29 @@ def sync(pending_result_keys) nil end - # @return [Boolean] True if this source has any pending requests for data. + # **Returns** + # + # - `Boolean` — True if this source has any pending requests for data. + # + # :call-seq: + # pending?() -> bool def pending? !@pending.empty? end # Add these key-value pairs to this source's cache # (future loads will use these merged values). - # @param new_results [Hash Object>] key-value pairs to cache in this source - # @return [void] + # + # **Parameters** + # + # - `new_results` (`Hash Object>`) — key-value pairs to cache in this source + # + # **Returns** + # + # - `void` + # + # :call-seq: + # merge(Hash[Object, Object] new_results) -> void def merge(new_results) new_results.each do |new_k, new_v| key = result_key_for(new_k) @@ -131,10 +201,12 @@ def merge(new_results) nil end - # Called by {GraphQL::Dataloader} to resolve and pending requests to this source. - # @api private - # @return [void] - def run_pending_keys + # Called by [GraphQL::Dataloader](rdoc-ref:GraphQL::Dataloader) to resolve and pending requests to this source. + # + # **Returns** + # + # - `void` + def run_pending_keys # :nodoc: @fetching.each_key { |k| @pending.delete(k) } return if @pending.empty? fetch_h = @pending @@ -167,9 +239,17 @@ def run_pending_keys # this method to call `.to_sql` on them, thus merging `.load(...)` calls when they apply # to equivalent relations. # - # @param batch_args [Array] - # @param batch_kwargs [Hash] - # @return [Object] + # **Parameters** + # + # - `batch_args` (`Array`) + # - `batch_kwargs` (`Hash`) + # + # **Returns** + # + # - `Object` + # + # :call-seq: + # batch_key_for(Array[Object] *batch_args, Hash **batch_kwargs) -> Object def self.batch_key_for(*batch_args, **batch_kwargs) if batch_kwargs.any? # rubocop:disable Development/NoneWithoutBlockCop [*batch_args, **batch_kwargs] @@ -179,7 +259,13 @@ def self.batch_key_for(*batch_args, **batch_kwargs) end # Clear any already-loaded objects for this source - # @return [void] + # + # **Returns** + # + # - `void` + # + # :call-seq: + # clear_cache() -> void def clear_cache @results.clear nil @@ -199,10 +285,15 @@ def add_pending_key(result_key, value) end # Reads and returns the result for the key from the internal cache, or raises an error if the result was an error - # @param key [Object] key passed to {#load} or {#load_all} - # @return [Object] The result from {#fetch} for `key`. - # @api private - def result_for(key) + # + # **Parameters** + # + # - `key` (`Object`) — key passed to [load](rdoc-ref:#load) or [load all](rdoc-ref:#load_all) + # + # **Returns** + # + # - `Object` — The result from [fetch](rdoc-ref:#fetch) for `key`. + def result_for(key) # :nodoc: if !@results.key?(key) raise GraphQL::InvariantError, <<-ERR Fetching result for a key on #{self.class} that hasn't been loaded yet (#{key.inspect}, loaded: #{@results.keys}) diff --git a/lib/graphql/date_encoding_error.rb b/lib/graphql/date_encoding_error.rb index f80b2eb0880..e139294c733 100644 --- a/lib/graphql/date_encoding_error.rb +++ b/lib/graphql/date_encoding_error.rb @@ -3,7 +3,7 @@ module GraphQL # This error is raised when `Types::ISO8601Date` is asked to return a value # that cannot be parsed to a Ruby Date. # - # @see GraphQL::Types::ISO8601Date which raises this error + # See [GraphQL::Types::ISO8601Date](rdoc-ref:GraphQL::Types::ISO8601Date) which raises this error class DateEncodingError < GraphQL::RuntimeTypeError # The value which couldn't be encoded attr_reader :date_value diff --git a/lib/graphql/dig.rb b/lib/graphql/dig.rb index 89be7130a16..fd2d86431a6 100644 --- a/lib/graphql/dig.rb +++ b/lib/graphql/dig.rb @@ -5,9 +5,17 @@ module Dig # so we can use some of the magic in Schema::InputObject and Interpreter::Arguments # to handle stringified/symbolized keys. # - # @param own_key [String, Symbol] A key to retrieve - # @param rest_keys [Array<[String, Symbol>] Retrieves the value object corresponding to the each key objects repeatedly - # @return [Object] + # **Parameters** + # + # - `own_key` (`String, Symbol`) — A key to retrieve + # - `rest_keys` (`Array<[String, Symbol]>`) — Keys to use for retrieving nested values + # + # **Returns** + # + # - `Object` + # + # :call-seq: + # dig(String | Symbol own_key, Array[[String, Symbol]] *rest_keys) -> Object def dig(own_key, *rest_keys) val = self[own_key] if val.nil? || rest_keys.empty? diff --git a/lib/graphql/duration_encoding_error.rb b/lib/graphql/duration_encoding_error.rb index 9611bb77950..fd490a5b12a 100644 --- a/lib/graphql/duration_encoding_error.rb +++ b/lib/graphql/duration_encoding_error.rb @@ -3,7 +3,7 @@ module GraphQL # This error is raised when `Types::ISO8601Duration` is asked to return a value # that cannot be parsed as an ISO8601-formatted duration by ActiveSupport::Duration. # - # @see GraphQL::Types::ISO8601Duration which raises this error + # See [GraphQL::Types::ISO8601Duration](rdoc-ref:GraphQL::Types::ISO8601Duration) which raises this error class DurationEncodingError < GraphQL::RuntimeTypeError # The value which couldn't be encoded attr_reader :duration_value diff --git a/lib/graphql/execution.rb b/lib/graphql/execution.rb index 5eba80dc3bc..5cbf2978822 100644 --- a/lib/graphql/execution.rb +++ b/lib/graphql/execution.rb @@ -9,8 +9,7 @@ module GraphQL module Execution - # @api private - class Skip < GraphQL::RuntimeError + class Skip < GraphQL::RuntimeError # :nodoc: attr_accessor :path def ast_nodes=(_ignored); end diff --git a/lib/graphql/execution/directive_checks.rb b/lib/graphql/execution/directive_checks.rb index d9fbe4e7dca..7badda2a85e 100644 --- a/lib/graphql/execution/directive_checks.rb +++ b/lib/graphql/execution/directive_checks.rb @@ -3,14 +3,18 @@ module GraphQL module Execution # Boolean checks for how an AST node's directives should # influence its execution - # @api private - module DirectiveChecks + module DirectiveChecks # :nodoc: SKIP = "skip" INCLUDE = "include" module_function - # @return [Boolean] Should this node be included in the query? + # **Returns** + # + # - `Boolean` — Should this node be included in the query? + # + # :call-seq: + # include?(directive_ast_nodes, query) -> bool def include?(directive_ast_nodes, query) directive_ast_nodes.each do |directive_ast_node| name = directive_ast_node.name diff --git a/lib/graphql/execution/errors.rb b/lib/graphql/execution/errors.rb index d4dcb775093..732484bc6c7 100644 --- a/lib/graphql/execution/errors.rb +++ b/lib/graphql/execution/errors.rb @@ -6,10 +6,18 @@ class Errors # Register this handler, updating the # internal handler index to maintain least-to-most specific. # - # @param error_class [Class] - # @param error_handlers [Hash] - # @param error_handler [Proc] - # @return [void] + # **Parameters** + # + # - `error_class` (`Class`) + # - `error_handlers` (`Hash`) + # - `error_handler` (`Proc`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # register_rescue_from(Class[Exception] error_class, Hash error_handlers, Proc error_handler) -> void def self.register_rescue_from(error_class, error_handlers, error_handler) subclasses_handlers = {} this_level_subclasses = [] @@ -52,7 +60,12 @@ def self.register_rescue_from(error_class, error_handlers, error_handler) nil end - # @return [Proc, nil] The handler for `error_class`, if one was registered on this schema or inherited + # **Returns** + # + # - `Proc, nil` — The handler for `error_class`, if one was registered on this schema or inherited + # + # :call-seq: + # find_handler_for(schema, error_class) -> Proc | nil def self.find_handler_for(schema, error_class) handlers = schema.error_handlers[:subclass_handlers] handler = nil diff --git a/lib/graphql/execution/interpreter.rb b/lib/graphql/execution/interpreter.rb index cafdafa756b..2716e2c7504 100644 --- a/lib/graphql/execution/interpreter.rb +++ b/lib/graphql/execution/interpreter.rb @@ -13,14 +13,22 @@ module Execution class Interpreter class << self # Used internally to signal that the query shouldn't be executed - # @api private + # :nodoc: NO_OPERATION = GraphQL::EmptyObjects::EMPTY_HASH - # @param schema [GraphQL::Schema] - # @param queries [Array] - # @param context [Hash] - # @param max_complexity [Integer, nil] - # @return [Array] One result per query + # **Parameters** + # + # - `schema` (`GraphQL::Schema`) + # - `queries` (`Array`) + # - `context` (`Hash`) + # - `max_complexity` (`Integer, nil`) + # + # **Returns** + # + # - `Array` — One result per query + # + # :call-seq: + # run_all(GraphQL::Schema schema, query_options, Hash context:, Integer | nil max_complexity:) -> Array[GraphQL::Query::Result] def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity) queries = query_options.map do |opts| query = case opts diff --git a/lib/graphql/execution/interpreter/argument_value.rb b/lib/graphql/execution/interpreter/argument_value.rb index ca7845b05ba..dfbd4a6dcc0 100644 --- a/lib/graphql/execution/interpreter/argument_value.rb +++ b/lib/graphql/execution/interpreter/argument_value.rb @@ -4,7 +4,7 @@ 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. + # See [Interpreter::Arguments#argument_values](rdoc-ref:Interpreter::Arguments#argument_values) for a hash of these objects. class ArgumentValue def initialize(definition:, value:, original_value:, default_used:) @definition = definition @@ -13,16 +13,36 @@ def initialize(definition:, value:, original_value:, default_used:) @default_used = default_used end - # @return [Object] The Ruby-ready value for this Argument + # **Returns** + # + # - `Object` — The Ruby-ready value for this Argument + # + # :call-seq: + # value -> Object attr_reader :value - # @return [Object] The value of this argument _before_ `prepare` is applied. + # **Returns** + # + # - `Object` — The value of this argument _before_ `prepare` is applied. + # + # :call-seq: + # original_value -> Object attr_reader :original_value - # @return [GraphQL::Schema::Argument] The definition instance for this argument + # **Returns** + # + # - `GraphQL::Schema::Argument` — The definition instance for this argument + # + # :call-seq: + # definition -> GraphQL::Schema::Argument attr_reader :definition - # @return [Boolean] `true` if the schema-defined `default_value:` was applied in this case. (No client-provided value was present.) + # **Returns** + # + # - `Boolean` — `true` if the schema-defined `default_value:` was applied in this case. (No client-provided value was present.) + # + # :call-seq: + # default_used?() -> bool def default_used? @default_used end diff --git a/lib/graphql/execution/interpreter/arguments.rb b/lib/graphql/execution/interpreter/arguments.rb index 4da23f25d4a..69f952921f1 100644 --- a/lib/graphql/execution/interpreter/arguments.rb +++ b/lib/graphql/execution/interpreter/arguments.rb @@ -8,7 +8,7 @@ class Interpreter # This object is immutable so that the runtime code can be sure that # modifications don't leak from one use to another # - # @see GraphQL::Query#arguments_for to get access to these objects. + # See [GraphQL::Query#arguments_for](rdoc-ref:GraphQL::Query#arguments_for) to get access to these objects. class Arguments extend Forwardable include GraphQL::Dig @@ -16,11 +16,21 @@ class Arguments # The Ruby-style arguments hash, ready for a resolver. # This hash is the one used at runtime. # - # @return [Hash] + # **Returns** + # + # - `Hash` + # + # :call-seq: + # keyword_arguments -> Hash[Symbol, Object] attr_reader :keyword_arguments - # @param argument_values [nil, Hash{Symbol => ArgumentValue}] - # @param keyword_arguments [nil, Hash{Symbol => Object}] + # **Parameters** + # + # - `argument_values` (`nil, Hash{Symbol => ArgumentValue}`) + # - `keyword_arguments` (`nil, Hash{Symbol => Object}`) + # + # :call-seq: + # initialize(nil | Hash[Symbol, Object] keyword_arguments:, nil | Hash[Symbol, ArgumentValue] argument_values:) def initialize(keyword_arguments: nil, argument_values:) @empty = argument_values.nil? || argument_values.empty? # This is only present when `extras` have been merged in: @@ -52,7 +62,12 @@ def initialize(keyword_arguments: nil, argument_values:) freeze end - # @return [Hash{Symbol => ArgumentValue}] + # **Returns** + # + # - `Hash{Symbol => ArgumentValue}` + # + # :call-seq: + # argument_values -> Hash[Symbol, ArgumentValue] attr_reader :argument_values def empty? @@ -70,10 +85,15 @@ def inspect # # This is called by the runtime to implement field `extras: [...]` # - # @param extra_args [Hash Object>] - # @return [Interpreter::Arguments] - # @api private - def merge_extras(extra_args) + # + # **Parameters** + # + # - `extra_args` (`Hash Object>`) + # + # **Returns** + # + # - `Interpreter::Arguments` + def merge_extras(extra_args) # :nodoc: self.class.new( argument_values: argument_values, keyword_arguments: keyword_arguments.merge(extra_args) diff --git a/lib/graphql/execution/interpreter/arguments_cache.rb b/lib/graphql/execution/interpreter/arguments_cache.rb index ec5482d865a..a8f09f2d31d 100644 --- a/lib/graphql/execution/interpreter/arguments_cache.rb +++ b/lib/graphql/execution/interpreter/arguments_cache.rb @@ -36,7 +36,7 @@ def cached_arguments_for(ast_node, argument_owner) @storage[argument_owner][nil][ast_node] end - # @yield [Interpreter::Arguments, Lazy] The finally-loaded arguments + # **Yields:** [Interpreter::Arguments, Lazy] The finally-loaded arguments def dataload_for(ast_node, argument_owner, parent_object, &block) # First, normalize all AST or Ruby values to a plain Ruby hash arg_storage = @storage[argument_owner][parent_object] diff --git a/lib/graphql/execution/interpreter/resolve.rb b/lib/graphql/execution/interpreter/resolve.rb index 102ceacb3cc..76344eb137b 100644 --- a/lib/graphql/execution/interpreter/resolve.rb +++ b/lib/graphql/execution/interpreter/resolve.rb @@ -5,15 +5,21 @@ module Execution class Interpreter module Resolve # Continue field results in `results` until there's nothing else to continue. - # @return [void] - # @deprecated Call `dataloader.run` instead + # **Deprecated:** Call `dataloader.run` instead + # + # **Returns** + # + # - `void` + # + # :call-seq: + # resolve_all(results, dataloader) -> void def self.resolve_all(results, dataloader) warn "#{self}.#{__method__} is deprecated; Use `dataloader.run` instead.#{caller(1, 5).map { |l| "\n #{l}"}.join}" dataloader.append_job { resolve(results, dataloader) } nil end - # @deprecated Call `dataloader.run` instead + # **Deprecated:** Call `dataloader.run` instead def self.resolve_each_depth(lazies_at_depth, dataloader) warn "#{self}.#{__method__} is deprecated; Use `dataloader.run` instead.#{caller(1, 5).map { |l| "\n #{l}"}.join}" @@ -39,7 +45,7 @@ def self.resolve_each_depth(lazies_at_depth, dataloader) nil end - # @deprecated Call `dataloader.run` instead + # **Deprecated:** Call `dataloader.run` instead def self.resolve(results, dataloader) warn "#{self}.#{__method__} is deprecated; Use `dataloader.run` instead.#{caller(1, 5).map { |l| "\n #{l}"}.join}" # There might be pending jobs here that _will_ write lazies diff --git a/lib/graphql/execution/interpreter/runtime.rb b/lib/graphql/execution/interpreter/runtime.rb index 029c00ac931..563684c5062 100644 --- a/lib/graphql/execution/interpreter/runtime.rb +++ b/lib/graphql/execution/interpreter/runtime.rb @@ -7,8 +7,7 @@ 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 + class Runtime # :nodoc: class CurrentState def initialize @current_field = nil @@ -26,13 +25,28 @@ def current_object :current_arguments, :current_field, :was_authorized_by_scope_items end - # @return [GraphQL::Query] + # **Returns** + # + # - `GraphQL::Query` + # + # :call-seq: + # query -> GraphQL::Query attr_reader :query - # @return [Class] + # **Returns** + # + # - `Class` + # + # :call-seq: + # schema -> Class[GraphQL::Schema] attr_reader :schema - # @return [GraphQL::Query::Context] + # **Returns** + # + # - `GraphQL::Query::Context` + # + # :call-seq: + # context -> GraphQL::Query::Context attr_reader :context def initialize(query:) @@ -63,7 +77,12 @@ def inspect "#<#{self.class.name} response=#{@response.inspect}>" end - # @return [void] + # **Returns** + # + # - `void` + # + # :call-seq: + # run_eager() -> void def run_eager root_type = query.root_type case query @@ -287,7 +306,12 @@ def gather_selections(graphql_response, owner_object, owner_type, selections, se NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH - # @return [void] + # **Returns** + # + # - `void` + # + # :call-seq: + # evaluate_selections(gathered_selections, selections_result, target_result, runtime_state) -> void def evaluate_selections(gathered_selections, selections_result, target_result, runtime_state) # rubocop:disable Metrics/ParameterLists runtime_state ||= get_current_runtime_state runtime_state.current_result_name = nil @@ -325,7 +349,12 @@ def evaluate_selections(gathered_selections, selections_result, target_result, r end end - # @return [void] + # **Returns** + # + # - `void` + # + # :call-seq: + # evaluate_selection(result_name, field_ast_nodes_or_ast_node, selections_result) -> void def evaluate_selection(result_name, field_ast_nodes_or_ast_node, selections_result) # rubocop:disable Metrics/ParameterLists return if selections_result.graphql_dead # As a performance optimization, the hash key will be a `Node` if @@ -655,7 +684,12 @@ def continue_value(value, field, is_non_null, ast_node, result_name, selection_r # # Location information from `path` and `ast_node`. # - # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later + # **Returns** + # + # - `Lazy, Array, Hash, Object` — Lazy, Array, and Hash are all traversed to resolve lazy values later + # + # :call-seq: + # continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state) -> Lazy | Array | Hash | Object def continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists if current_type.non_null? current_type = current_type.of_type @@ -898,11 +932,19 @@ def minimal_after_lazy(value, &block) end end - # @param obj [Object] Some user-returned value that may want to be batched - # @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. + # **Parameters** + # + # - `obj` (`Object`) — Some user-returned value that may want to be batched + # - `field` (`GraphQL::Schema::Field`) + # - `eager` (`Boolean`) — Set to `true` for mutation root fields only + # - `trace` (`Boolean`) — If `false`, don't wrap this with field tracing + # + # **Returns** + # + # - `GraphQL::Execution::Lazy, Object` — If loading `object` will be deferred, it's a wrapper over it. + # + # :call-seq: + # after_lazy(lazy_obj, GraphQL::Schema::Field field:, owner_object:, arguments:, ast_node:, result:, result_name:, bool eager:, runtime_state:, bool trace:, &block) -> GraphQL::Execution::Lazy | Object def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, runtime_state:, trace: true, &block) if lazy?(lazy_obj) was_authorized_by_scope_items = runtime_state.was_authorized_by_scope_items diff --git a/lib/graphql/execution/interpreter/runtime/graphql_result.rb b/lib/graphql/execution/interpreter/runtime/graphql_result.rb index e42ea4bd21f..19f93ff5ca3 100644 --- a/lib/graphql/execution/interpreter/runtime/graphql_result.rb +++ b/lib/graphql/execution/interpreter/runtime/graphql_result.rb @@ -54,7 +54,12 @@ def depth attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent, :graphql_application_value, :graphql_result_type, :graphql_selections, :graphql_is_eager, :ast_node, :graphql_arguments, :graphql_field - # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects) + # **Returns** + # + # - `Hash` — Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects) + # + # :call-seq: + # graphql_result_data -> Hash attr_accessor :graphql_result_data end diff --git a/lib/graphql/execution/lazy.rb b/lib/graphql/execution/lazy.rb index 3ad9f3e0505..ec93f6cbff5 100644 --- a/lib/graphql/execution/lazy.rb +++ b/lib/graphql/execution/lazy.rb @@ -10,20 +10,30 @@ module Execution # 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 + class Lazy # :nodoc: attr_reader :field - # Create a {Lazy} which will get its inner value by calling the block - # @param field [GraphQL::Schema::Field] - # @param get_value_func [Proc] a block to get the inner value (later) + # Create a [Lazy](rdoc-ref:Lazy) which will get its inner value by calling the block + # + # **Parameters** + # + # - `field` (`GraphQL::Schema::Field`) + # - `get_value_func` (`Proc`) — a block to get the inner value (later) + # + # :call-seq: + # initialize(GraphQL::Schema::Field field:, Proc &get_value_func) def initialize(field: nil, &get_value_func) @get_value_func = get_value_func @resolved = false @field = field end - # @return [Object] The wrapped value, calling the lazy block if necessary + # **Returns** + # + # - `Object` — The wrapped value, calling the lazy block if necessary + # + # :call-seq: + # value() -> Object def value if !@resolved @resolved = true @@ -45,15 +55,28 @@ def value end end - # @return [Lazy] A {Lazy} whose value depends on another {Lazy}, plus any transformations in `block` + # **Returns** + # + # - `Lazy` — A [Lazy](rdoc-ref:Lazy) whose value depends on another [Lazy](rdoc-ref:Lazy), plus any transformations in `block` + # + # :call-seq: + # then() -> Lazy def then self.class.new { yield(value) } end - # @param lazies [Array] Maybe-lazy objects - # @return [Lazy] A lazy which will sync all of `lazies` + # **Parameters** + # + # - `lazies` (`Array`) — Maybe-lazy objects + # + # **Returns** + # + # - `Lazy` — A lazy which will sync all of `lazies` + # + # :call-seq: + # all(Array[Object] lazies) -> Lazy def self.all(lazies) self.new { lazies.map { |l| l.is_a?(Lazy) ? l.value : l } @@ -61,7 +84,7 @@ def self.all(lazies) end # This can be used for fields which _had no_ lazy results - # @api private + # :nodoc: NullResult = Lazy.new(){} NullResult.value end diff --git a/lib/graphql/execution/lazy/lazy_method_map.rb b/lib/graphql/execution/lazy/lazy_method_map.rb index ffc9b3c882d..8d9d0ad89b8 100644 --- a/lib/graphql/execution/lazy/lazy_method_map.rb +++ b/lib/graphql/execution/lazy/lazy_method_map.rb @@ -9,13 +9,12 @@ module GraphQL module Execution class Lazy - # {GraphQL::Schema} uses this to match returned values to lazy resolution methods. + # [GraphQL::Schema](rdoc-ref: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 + # See the schema's `lazy?` configuration to understand which values use this map. + class LazyMethodMap # :nodoc: def initialize(use_concurrent: defined?(Concurrent::Map)) @storage = use_concurrent ? Concurrent::Map.new : ConcurrentishMap.new end @@ -24,14 +23,27 @@ 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 + # **Parameters** + # + # - `lazy_class` (`Class`) — A class which represents a lazy value (subclasses may also be used) + # - `lazy_value_method` (`Symbol`) — The method to call on this class to get its value + # + # :call-seq: + # set(Class lazy_class, Symbol lazy_value_method) 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 + # **Parameters** + # + # - `value` (`Object`) — an object which may have a `lazy_value_method` registered for its class or superclasses + # + # **Returns** + # + # - `Symbol, nil` — The `lazy_value_method` for this object, or nil + # + # :call-seq: + # get(Object value) -> Symbol | nil def get(value) @storage.compute_if_absent(value.class) { find_superclass_method(value.class) } end diff --git a/lib/graphql/execution/lookahead.rb b/lib/graphql/execution/lookahead.rb index 61aa94aae5e..92bf89a3660 100644 --- a/lib/graphql/execution/lookahead.rb +++ b/lib/graphql/execution/lookahead.rb @@ -9,28 +9,38 @@ module Execution # 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] + # **Examples** # - # # 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 + # **Example: looking ahead in a field** + # + # ```ruby + # 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 + # **Parameters** + # + # - `query` (`GraphQL::Query`) + # - `ast_nodes` (`Array, Array`) + # - `field` (`GraphQL::Schema::Field`) — if `ast_nodes` are fields, this is the field definition matching those nodes + # - `root_type` (`Class`) — if `ast_nodes` are operation definition, this is the root type for that operation + # + # :call-seq: + # initialize(GraphQL::Query query:, Array[GraphQL::Language::Nodes::Field] | Array[GraphQL::Language::Nodes::OperationDefinition] ast_nodes:, GraphQL::Schema::Field field:, Class root_type:, owner_type:) def initialize(query:, ast_nodes:, field: nil, root_type: nil, owner_type: nil) @ast_nodes = ast_nodes.freeze @field = field @@ -40,16 +50,36 @@ def initialize(query:, ast_nodes:, field: nil, root_type: nil, owner_type: nil) @owner_type = owner_type end - # @return [Array] + # **Returns** + # + # - `Array` + # + # :call-seq: + # ast_nodes -> Array[GraphQL::Language::Nodes::Field] attr_reader :ast_nodes - # @return [GraphQL::Schema::Field] + # **Returns** + # + # - `GraphQL::Schema::Field` + # + # :call-seq: + # field -> GraphQL::Schema::Field attr_reader :field - # @return [GraphQL::Schema::Object, GraphQL::Schema::Union, GraphQL::Schema::Interface] + # **Returns** + # + # - `GraphQL::Schema::Object, GraphQL::Schema::Union, GraphQL::Schema::Interface` + # + # :call-seq: + # owner_type -> GraphQL::Schema::Object | GraphQL::Schema::Union | GraphQL::Schema::Interface attr_reader :owner_type - # @return [Hash] + # **Returns** + # + # - `Hash` + # + # :call-seq: + # arguments() -> Hash[Symbol, Object] def arguments if defined?(@arguments) @arguments @@ -80,9 +110,18 @@ def arguments # 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] + # + # **Parameters** + # + # - `field_name` (`String, Symbol`) + # - `arguments` (`Hash`) — Arguments which must match in the selection + # + # **Returns** + # + # - `Boolean` + # + # :call-seq: + # selects?(String | Symbol field_name, selected_type:, Hash arguments:) -> bool def selects?(field_name, selected_type: @selected_type, arguments: nil) selection(field_name, selected_type: selected_type, arguments: arguments).selected? end @@ -96,22 +135,45 @@ def selects?(field_name, selected_type: @selected_type, arguments: nil) # 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 alias_name [String, Symbol] - # @param arguments [Hash] Arguments which must match in the selection - # @return [Boolean] + # + # **Parameters** + # + # - `alias_name` (`String, Symbol`) + # - `arguments` (`Hash`) — Arguments which must match in the selection + # + # **Returns** + # + # - `Boolean` + # + # :call-seq: + # selects_alias?(String | Symbol alias_name, Hash arguments:) -> bool def selects_alias?(alias_name, arguments: nil) alias_selection(alias_name, arguments: arguments).selected? end - # @return [Boolean] True if this lookahead represents a field that was requested + # **Returns** + # + # - `Boolean` — True if this lookahead represents a field that was requested + # + # :call-seq: + # selected?() -> bool def selected? true end - # Like {#selects?}, but can be used for chaining. - # It returns a null object (check with {#selected?}) - # @param field_name [String, Symbol] - # @return [GraphQL::Execution::Lookahead] + # Like [selects?](rdoc-ref:#selects?), but can be used for chaining. + # It returns a null object (check with [selected?](rdoc-ref:#selected?)) + # + # **Parameters** + # + # - `field_name` (`String, Symbol`) + # + # **Returns** + # + # - `GraphQL::Execution::Lookahead` + # + # :call-seq: + # selection(String | Symbol field_name, selected_type:, arguments:) -> GraphQL::Execution::Lookahead def selection(field_name, selected_type: @selected_type, arguments: nil) next_field_defn = case field_name when String @@ -141,9 +203,15 @@ def selection(field_name, selected_type: @selected_type, arguments: nil) lookahead_for_selection(next_field_defn, selected_type, arguments) end - # Like {#selection}, but for aliases. - # It returns a null object (check with {#selected?}) - # @return [GraphQL::Execution::Lookahead] + # Like [selection](rdoc-ref:#selection), but for aliases. + # It returns a null object (check with [selected?](rdoc-ref:#selected?)) + # + # **Returns** + # + # - `GraphQL::Execution::Lookahead` + # + # :call-seq: + # alias_selection(alias_name, selected_type:, arguments:) -> GraphQL::Execution::Lookahead def alias_selection(alias_name, selected_type: @selected_type, arguments: nil) alias_cache_key = [alias_name, arguments] return alias_selections[key] if alias_selections.key?(alias_name) @@ -163,21 +231,34 @@ def alias_selection(alias_name, selected_type: @selected_type, arguments: nil) alias_selections[alias_cache_key] = lookahead_for_selection(next_field_defn, selected_type, alias_arguments, alias_name) end - # Like {#selection}, but for all nodes. + # Like [selection](rdoc-ref:#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 + # **Examples** # - # @param arguments [Hash] Arguments which must match in the selection - # @return [Array] + # **Example: getting the name of a selection** + # + # ```ruby + # def articles(lookahead:) + # next_lookaheads = lookahead.selections # => [#, ...] + # next_lookaheads.map(&:name) #=> [:full_content, :title] + # end + # ``` + # + # **Parameters** + # + # - `arguments` (`Hash`) — Arguments which must match in the selection + # + # **Returns** + # + # - `Array` + # + # :call-seq: + # selections(Hash arguments:) -> Array[GraphQL::Execution::Lookahead] def selections(arguments: nil) subselections_by_type = {} subselections_on_type = subselections_by_type[@selected_type] = {} @@ -202,13 +283,23 @@ def selections(arguments: nil) # 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 + # **Examples** + # + # **Example: getting the name of a selection** + # + # ```ruby + # def articles(lookahead:) + # article.selection(:full_content).name # => :full_content + # # ... + # end + # ``` + # + # **Returns** + # + # - `Symbol` # - # @return [Symbol] + # :call-seq: + # name() -> Symbol def name @field && @field.original_name end diff --git a/lib/graphql/execution/multiplex.rb b/lib/graphql/execution/multiplex.rb index a23efd9e3de..0cc97d53843 100644 --- a/lib/graphql/execution/multiplex.rb +++ b/lib/graphql/execution/multiplex.rb @@ -17,12 +17,11 @@ module Execution # # If one query raises an application error, all queries will be in undefined states. # - # Validation errors and {GraphQL::ExecutionError}s are handled in isolation: + # Validation errors and [GraphQL::ExecutionError](rdoc-ref: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 + # See [Schema.multiplex](rdoc-ref:GraphQL::Schema::multiplex) for the public API + class Multiplex # :nodoc: include Tracing::Traceable attr_reader :context, :queries, :schema, :max_complexity, :dataloader, :current_trace diff --git a/lib/graphql/execution/runner.rb b/lib/graphql/execution/runner.rb index 4b780dca72c..abf6fc817d8 100644 --- a/lib/graphql/execution/runner.rb +++ b/lib/graphql/execution/runner.rb @@ -55,7 +55,12 @@ def add_step(step) attr_reader :steps_queue, :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers, :input_values - # @return [void] + # **Returns** + # + # - `void` + # + # :call-seq: + # add_finalizer(query, result_value, key, finalizer) -> void def add_finalizer(query, result_value, key, finalizer) @finalizers ||= {}.compare_by_identity f_for_query = @finalizers[query] ||= {}.compare_by_identity diff --git a/lib/graphql/execution_error.rb b/lib/graphql/execution_error.rb index b79d05fc944..dcc7e5cd1a6 100644 --- a/lib/graphql/execution_error.rb +++ b/lib/graphql/execution_error.rb @@ -4,18 +4,30 @@ module GraphQL # the error will be inserted into the response's `"errors"` key # and the field will resolve to `nil`. class ExecutionError < GraphQL::RuntimeError - # @return [String] an array describing the JSON-path into the execution - # response which corresponds to this error. + # **Returns** + # + # - `String` — an array describing the JSON-path into the execution response which corresponds to this error. + # + # :call-seq: + # path -> String 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. + # **Deprecated:** Use `extensions` instead of `options`. The GraphQL spec recommends that any custom entries in an error be under the `extensions` key. + # + # **Returns** + # + # - `Hash` — Optional data for error objects + # + # :call-seq: + # options -> Hash attr_accessor :options - # @return [Hash] Optional custom data for error objects which will be added - # under the `extensions` key. + # **Returns** + # + # - `Hash` — Optional custom data for error objects which will be added under the `extensions` key. + # + # :call-seq: + # extensions -> Hash attr_accessor :extensions def initialize(message, ast_node: nil, ast_nodes: nil, options: nil, extensions: nil) @@ -38,7 +50,12 @@ def finalize_graphql_result(query, result_data, key) end end - # @return [Hash] An entry for the response's "errors" key + # **Returns** + # + # - `Hash` — An entry for the response's "errors" key + # + # :call-seq: + # to_h() -> Hash def to_h hash = { "message" => message, diff --git a/lib/graphql/integer_decoding_error.rb b/lib/graphql/integer_decoding_error.rb index 30a5ae823c4..b7fe92a9f94 100644 --- a/lib/graphql/integer_decoding_error.rb +++ b/lib/graphql/integer_decoding_error.rb @@ -4,7 +4,7 @@ module GraphQL # # For really big integer values, consider `GraphQL::Types::BigInt` # - # @see GraphQL::Types::Int which raises this error + # See [GraphQL::Types::Int](rdoc-ref:GraphQL::Types::Int) which raises this error class IntegerDecodingError < GraphQL::RuntimeTypeError # The value which couldn't be decoded attr_reader :integer_value diff --git a/lib/graphql/integer_encoding_error.rb b/lib/graphql/integer_encoding_error.rb index 5de3b52b34f..d486adb7791 100644 --- a/lib/graphql/integer_encoding_error.rb +++ b/lib/graphql/integer_encoding_error.rb @@ -7,15 +7,25 @@ module GraphQL # - `ID` for database primary keys or other identifiers # - `GraphQL::Types::BigInt` for really big integer values # - # @see GraphQL::Types::Int which raises this error + # See [GraphQL::Types::Int](rdoc-ref:GraphQL::Types::Int) which raises this error class IntegerEncodingError < GraphQL::RuntimeTypeError # The value which couldn't be encoded attr_reader :integer_value - # @return [GraphQL::Schema::Field] The field that returned a too-big integer + # **Returns** + # + # - `GraphQL::Schema::Field` — The field that returned a too-big integer + # + # :call-seq: + # field -> GraphQL::Schema::Field attr_reader :field - # @return [Array] Where the field appeared in the GraphQL response + # **Returns** + # + # - `Array` — Where the field appeared in the GraphQL response + # + # :call-seq: + # path -> Array[String | Integer] attr_reader :path def initialize(value, context:) diff --git a/lib/graphql/invalid_null_error.rb b/lib/graphql/invalid_null_error.rb index ff738bb2314..d34498da24f 100644 --- a/lib/graphql/invalid_null_error.rb +++ b/lib/graphql/invalid_null_error.rb @@ -3,20 +3,40 @@ module GraphQL # Raised automatically when a field's resolve function returns `nil` # for a non-null field. class InvalidNullError < GraphQL::RuntimeError - # @return [GraphQL::BaseType] The owner of {#field} + # **Returns** + # + # - `GraphQL::BaseType` — The owner of [field](rdoc-ref:#field) + # + # :call-seq: + # parent_type -> GraphQL::BaseType attr_reader :parent_type - # @return [GraphQL::Field] The field which failed to return a value + # **Returns** + # + # - `GraphQL::Field` — The field which failed to return a value + # + # :call-seq: + # field -> GraphQL::Field attr_reader :field - # @return [GraphQL::Language::Nodes::Field] the field where the error occurred + # **Returns** + # + # - `GraphQL::Language::Nodes::Field` — the field where the error occurred + # + # :call-seq: + # ast_node() -> GraphQL::Language::Nodes::Field def ast_node @ast_nodes.first end attr_reader :ast_nodes - # @return [Boolean] indicates an array result caused the error + # **Returns** + # + # - `Boolean` — indicates an array result caused the error + # + # :call-seq: + # is_from_array -> bool attr_reader :is_from_array attr_accessor :path diff --git a/lib/graphql/language.rb b/lib/graphql/language.rb index d8f967ac16a..0714cf6f44e 100644 --- a/lib/graphql/language.rb +++ b/lib/graphql/language.rb @@ -16,8 +16,7 @@ module GraphQL module Language - # @api private - def self.serialize(value) + def self.serialize(value) # :nodoc: if value.is_a?(Hash) serialized_hash = value.map do |k, v| "#{k}:#{serialize v}" @@ -43,7 +42,13 @@ def self.serialize(value) # Returns a new string if any single-quoted newlines were escaped. # Otherwise, returns `query_str` unchanged. - # @return [String] + # + # **Returns** + # + # - `String` + # + # :call-seq: + # escape_single_quoted_newlines(query_str) -> String def self.escape_single_quoted_newlines(query_str) scanner = StringScanner.new(query_str) inside_single_quoted_string = false diff --git a/lib/graphql/language/cache.rb b/lib/graphql/language/cache.rb index 5f30ad2f905..6fd0072294f 100644 --- a/lib/graphql/language/cache.rb +++ b/lib/graphql/language/cache.rb @@ -5,7 +5,7 @@ module GraphQL module Language - # This cache is used by {GraphQL::Language::Parser.parse_file} when it's enabled. + # This cache is used by [GraphQL::Language::Parser.parse_file](rdoc-ref:GraphQL::Language::Parser.parse_file) when it's enabled. # # With Rails, parser caching may enabled by setting `config.graphql.parser_cache = true` in your Rails application. # @@ -16,7 +16,7 @@ module Language # You will need to clear the cache directory for each new deployment of your application. # Also note that the parser cache will grow as your schema is loaded, so the cache directory must be writable. # - # @see GraphQL::Railtie for simple Rails integration + # See [GraphQL::Railtie](rdoc-ref:GraphQL::Railtie) for simple Rails integration class Cache def initialize(path) @path = path diff --git a/lib/graphql/language/document_from_schema_definition.rb b/lib/graphql/language/document_from_schema_definition.rb index 4674230cdb7..375a27c02ae 100644 --- a/lib/graphql/language/document_from_schema_definition.rb +++ b/lib/graphql/language/document_from_schema_definition.rb @@ -1,18 +1,16 @@ # 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. + # **Parameters** # - # @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 + # - `context` (`Hash`) + # - `only` (`<#call(member, ctx)>`) + # - `except` (`<#call(member, ctx)>`) + # - `include_introspection_types` (`Boolean`) — Whether or not to include introspection types in the AST + # - `include_built_in_scalars` (`Boolean`) — Whether or not to include built in scalars in the AST + # - `include_built_in_directives` (`Boolean`) — Whether or not to include built in directives in the AST + class DocumentFromSchemaDefinition # :nodoc: def initialize( schema, context: nil, include_introspection_types: false, include_built_in_directives: false, include_built_in_scalars: false, always_include_schema: false diff --git a/lib/graphql/language/generation.rb b/lib/graphql/language/generation.rb index 6059a264b18..3427a853e97 100644 --- a/lib/graphql/language/generation.rb +++ b/lib/graphql/language/generation.rb @@ -7,15 +7,28 @@ module Generation # 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` + # **Examples** + # + # **Example: Turning a document into a query** + # + # ```ruby + # document = GraphQL.parse(query_string) + # GraphQL::Language::Generation.generate(document) + # # => "{ ... }" + # ``` + # + # **Parameters** + # + # - `node` (`GraphQL::Language::Nodes::AbstractNode`) — an AST node to recursively stringify + # - `indent` (`String`) — Whitespace to add to each printed node + # - `printer` (`GraphQL::Language::Printer`) — An optional custom printer for printing AST nodes. Defaults to GraphQL::Language::Printer + # + # **Returns** + # + # - `String` — Valid GraphQL for `node` + # + # :call-seq: + # generate(GraphQL::Language::Nodes::AbstractNode node, String indent:, GraphQL::Language::Printer printer:) -> String def generate(node, indent: "", printer: GraphQL::Language::Printer.new) printer.print(node, indent: indent) end diff --git a/lib/graphql/language/nodes.rb b/lib/graphql/language/nodes.rb index 80928c5f7c1..c3679578001 100644 --- a/lib/graphql/language/nodes.rb +++ b/lib/graphql/language/nodes.rb @@ -12,8 +12,14 @@ module Nodes 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) + # This AST node's [line](rdoc-ref:GraphQL::Language::Nodes::AbstractNode#line) returns the first line, which may be the description. + # + # **Returns** + # + # - `Integer` — The first line of the definition (not the description) + # + # :call-seq: + # definition_line -> Integer attr_reader :definition_line def initialize(definition_line: nil, **_rest) @@ -46,7 +52,13 @@ def definition_line end # Value equality - # @return [Boolean] True if `self` is equivalent to `other` + # + # **Returns** + # + # - `Boolean` — True if `self` is equivalent to `other` + # + # :call-seq: + # ==(other) -> bool def ==(other) return true if equal?(other) other.kind_of?(self.class) && @@ -56,12 +68,22 @@ def ==(other) NO_CHILDREN = GraphQL::EmptyObjects::EMPTY_ARRAY - # @return [Array] all nodes in the tree below this one + # **Returns** + # + # - `Array` — all nodes in the tree below this one + # + # :call-seq: + # children() -> Array[GraphQL::Language::Nodes::AbstractNode] def children NO_CHILDREN end - # @return [Array] Scalar values attached to this node + # **Returns** + # + # - `Array` — Scalar values attached to this node + # + # :call-seq: + # scalars() -> Array[Integer | Float | String | bool | Array] def scalars NO_CHILDREN end @@ -94,8 +116,17 @@ def to_query_string(printer: GraphQL::Language::Printer.new) end # This creates a copy of `self`, with `new_options` applied. - # @param new_options [Hash] - # @return [AbstractNode] a shallow copy of `self` + # + # **Parameters** + # + # - `new_options` (`Hash`) + # + # **Returns** + # + # - `AbstractNode` — a shallow copy of `self` + # + # :call-seq: + # merge(Hash new_options) -> AbstractNode def merge(new_options) dup.merge!(new_options) end @@ -370,11 +401,13 @@ class Argument < AbstractNode scalar_methods :name, :value children_methods(false) - # @!attribute name - # @return [String] the key for this argument + # **Attributes** + # + # - `name` (`String`) — the key for this argument - # @!attribute value - # @return [String, Float, Integer, Boolean, Array, InputObject, VariableIdentifier] The value passed for this key + # **Attributes** + # + # - `value` (`String, Float, Integer, Boolean, Array, InputObject, VariableIdentifier`) — The value passed for this key def children @children ||= Array(value).flatten.tap { _1.select! { |v| v.is_a?(AbstractNode) } } @@ -488,8 +521,9 @@ class FragmentSpread < AbstractNode self.children_method_name = :selections - # @!attribute name - # @return [String] The identifier of the fragment to apply, corresponds with {FragmentDefinition#name} + # **Attributes** + # + # - `name` (`String`) — The identifier of the fragment to apply, corresponds with [FragmentDefinition#name](rdoc-ref:FragmentDefinition#name) end # An unnamed fragment, defined directly in the query with `... { }` @@ -502,8 +536,9 @@ class InlineFragment < AbstractNode 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 + # **Attributes** + # + # - `type` (`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 @@ -511,10 +546,16 @@ 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 + # **Attributes** + # + # - `arguments` (`Array`) — A list of key-value pairs inside this input object - # @return [Hash] Recursively turn this input object into a Ruby Hash + # **Returns** + # + # - `Hash` — Recursively turn this input object into a Ruby Hash + # + # :call-seq: + # to_h(options=) -> Hash[String, Any] def to_h(options={}) arguments.inject({}) do |memo, pair| v = pair.value @@ -557,14 +598,17 @@ class NonNullType < WrapperType class VariableDefinition < AbstractNode scalar_methods :name, :type, :default_value children_methods(directives: Directive) - # @!attribute default_value - # @return [String, Integer, Float, Boolean, Array, NullValue] A Ruby value to use if no other value is provided + # **Attributes** + # + # - `default_value` (`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 + # **Attributes** + # + # - `type` (`TypeName, NonNullType, ListType`) — The expected type of this value - # @!attribute name - # @return [String] The identifier for this variable, _without_ `$` + # **Attributes** + # + # - `name` (`String`) — The identifier for this variable, _without_ `$` self.children_method_name = :variables end @@ -580,44 +624,59 @@ class OperationDefinition < AbstractNode selections: GraphQL::Language::Nodes::Field, }) - # @!attribute variables - # @return [Array] Variable $definitions for this operation + # **Attributes** + # + # - `variables` (`Array`) — Variable $definitions for this operation - # @!attribute selections - # @return [Array] Root-level fields on this operation + # **Attributes** + # + # - `selections` (`Array`) — Root-level fields on this operation - # @!attribute operation_type - # @return [String, nil] The root type for this operation, or `nil` for implicit `"query"` + # **Attributes** + # + # - `operation_type` (`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 + # **Attributes** + # + # - `name` (`String, nil`) — The name for this operation, or `nil` if unnamed self.children_method_name = :definitions end # This is the AST root for normal queries # - # @example Deriving a document by parsing a string - # document = GraphQL.parse(query_string) + # **Examples** # - # @example Creating a string from a document - # document.to_query_string - # # { ... } + # **Example: Deriving a document by parsing a string** # - # @example Creating a custom string from a document - # class VariableScrubber < GraphQL::Language::Printer - # def print_argument(arg) - # print_string("#{arg.name}: ") - # end - # end + # ```ruby + # document = GraphQL.parse(query_string) + # ``` # - # document.to_query_string(printer: VariableScrubber.new) + # **Example: Creating a string from a document** # + # ```ruby + # document.to_query_string + # # { ... } + # ``` + # + # **Example: Creating a custom string from a document** + # + # ```ruby + # class VariableScrubber < GraphQL::Language::Printer + # def print_argument(arg) + # print_string("#{arg.name}: ") + # end + # end + # + # document.to_query_string(printer: VariableScrubber.new) + # ``` class Document < AbstractNode scalar_methods false children_methods(definitions: nil) - # @!attribute definitions - # @return [Array] top-level GraphQL units: operations or fragments + # **Attributes** + # + # - `definitions` (`Array`) — top-level GraphQL units: operations or fragments def slice_definition(name) GraphQL::Language::DefinitionSlice.slice(self, name) diff --git a/lib/graphql/language/parser.rb b/lib/graphql/language/parser.rb index 23ed0bf301c..0fc36f1c944 100644 --- a/lib/graphql/language/parser.rb +++ b/lib/graphql/language/parser.rb @@ -76,7 +76,12 @@ def column_at(pos) private - # @return [Array] Positions of each line break in the original string + # **Returns** + # + # - `Array` — Positions of each line break in the original string + # + # :call-seq: + # lines_at() -> Array[Integer] def lines_at @lines_at ||= begin la = [] diff --git a/lib/graphql/language/printer.rb b/lib/graphql/language/printer.rb index c861f33404d..f8b88292091 100644 --- a/lib/graphql/language/printer.rb +++ b/lib/graphql/language/printer.rb @@ -30,27 +30,41 @@ def to_string # 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) - # # => "{ ... }" + # **Examples** # + # **Example: Turning a document into a query string** # - # @example Building a custom printer + # ```ruby + # document = GraphQL.parse(query_string) + # GraphQL::Language::Printer.new.print(document) + # # => "{ ... }" + # ``` # - # class MyPrinter < GraphQL::Language::Printer - # def print_argument(arg) - # print_string("#{arg.name}: ") - # end - # end + # **Example: Building a custom printer** # - # MyPrinter.new.print(document) - # # => "mutation { pay(creditCard: ) { success } }" + # ```ruby + # class MyPrinter < GraphQL::Language::Printer + # def print_argument(arg) + # print_string("#{arg.name}: ") + # end + # end # - # @param node [Nodes::AbstractNode] - # @param indent [String] Whitespace to add to the printed node - # @param truncate_size [Integer, nil] The size to truncate to. - # @return [String] Valid GraphQL for `node` + # MyPrinter.new.print(document) + # # => "mutation { pay(creditCard: ) { success } }" + # ``` + # + # **Parameters** + # + # - `node` (`Nodes::AbstractNode`) + # - `indent` (`String`) — Whitespace to add to the printed node + # - `truncate_size` (`Integer, nil`) — The size to truncate to. + # + # **Returns** + # + # - `String` — Valid GraphQL for `node` + # + # :call-seq: + # print(Nodes::AbstractNode node, String indent:, Integer | nil truncate_size:) -> String def print(node, indent: "", truncate_size: nil) truncate_size = truncate_size ? [truncate_size - OMISSION.size, 0].max : nil @out = TruncatableBuffer.new(truncate_size: truncate_size) diff --git a/lib/graphql/language/sanitized_printer.rb b/lib/graphql/language/sanitized_printer.rb index 82d576ff63d..64775994b7e 100644 --- a/lib/graphql/language/sanitized_printer.rb +++ b/lib/graphql/language/sanitized_printer.rb @@ -10,11 +10,16 @@ module Language # 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](rdoc-ref:Query#sanitized_query_string) # - # @see {Query#sanitized_query_string} + # **Examples** + # + # **Example: Printing a scrubbed string** + # + # ```ruby + # printer = QueryPrinter.new(query) + # puts printer.sanitized_query_string + # ``` class SanitizedPrinter < GraphQL::Language::Printer REDACTED = "\"\"" @@ -27,7 +32,12 @@ def initialize(query, inline_variables: true) @inline_variables = inline_variables end - # @return [String, nil] A scrubbed query string, if the query was valid. + # **Returns** + # + # - `String, nil` — A scrubbed query string, if the query was valid. + # + # :call-seq: + # sanitized_query_string() -> String | nil def sanitized_query_string if query.valid? print(query.document) diff --git a/lib/graphql/language/static_visitor.rb b/lib/graphql/language/static_visitor.rb index 6a7dfcbf425..0444501ec5e 100644 --- a/lib/graphql/language/static_visitor.rb +++ b/lib/graphql/language/static_visitor.rb @@ -9,7 +9,13 @@ def initialize(document) end # Visit `document` and all children - # @return [void] + # + # **Returns** + # + # - `void` + # + # :call-seq: + # visit() -> void def visit # `@document` may be any kind of node: visit_method = @document.visit_method @@ -104,9 +110,17 @@ def self.make_visit_methods(ast_node_class) # To customize this hook, override one of its make_visit_methods (or the base method?) # in your subclasses. # - # @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 [void] + # **Parameters** + # + # - `node` (`GraphQL::Language::Nodes::AbstractNode`) — the node being visited + # - `parent` (`GraphQL::Language::Nodes::AbstractNode, nil`) — the previously-visited node, or `nil` if this is the root node. + # + # **Returns** + # + # - `void` + # + # :call-seq: + # #{node_method}(GraphQL::Language::Nodes::AbstractNode node, GraphQL::Language::Nodes::AbstractNode | nil parent) -> void def #{node_method}(node, parent) #{ if method_defined?(child_visit_method) diff --git a/lib/graphql/language/visitor.rb b/lib/graphql/language/visitor.rb index e2e842fb012..dadc4e77c29 100644 --- a/lib/graphql/language/visitor.rb +++ b/lib/graphql/language/visitor.rb @@ -3,35 +3,40 @@ 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 + # See [GraphQL::Language::StaticVisitor](rdoc-ref:GraphQL::Language::StaticVisitor) for a faster visitor that doesn't support modifying the document # - # attr_reader :count + # **Examples** # - # 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 + # **Example: Create a visitor counting certain field names** + # + # ```ruby + # class NameCounter < GraphQL::Language::Visitor + # def initialize(document, field_name) + # super(document) + # @field_name = field_name + # @count = 0 # end # - # # Initialize a visitor - # visitor = NameCounter.new(document, "name") - # # Run it - # visitor.visit - # # Check the result - # visitor.count - # # => 3 + # 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 # - # @see GraphQL::Language::StaticVisitor for a faster visitor that doesn't support modifying the document + # # Initialize a visitor + # visitor = NameCounter.new(document, "name") + # # Run it + # visitor.visit + # # Check the result + # visitor.count + # # => 3 + # ``` class Visitor class DeleteNode; end @@ -44,11 +49,22 @@ def initialize(document) @result = nil end - # @return [GraphQL::Language::Nodes::Document] The document with any modifications applied + # **Returns** + # + # - `GraphQL::Language::Nodes::Document` — The document with any modifications applied + # + # :call-seq: + # result -> GraphQL::Language::Nodes::Document attr_reader :result # Visit `document` and all children - # @return [void] + # + # **Returns** + # + # - `void` + # + # :call-seq: + # visit() -> void def visit # `@document` may be any kind of node: visit_method = :"#{@document.visit_method}_with_modifications" @@ -175,9 +191,17 @@ def self.make_visit_methods(ast_node_class) # To customize this hook, override one of its make_visit_methods (or the base method?) # in your subclasses. # - # @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`. + # **Parameters** + # + # - `node` (`GraphQL::Language::Nodes::AbstractNode`) — the node being visited + # - `parent` (`GraphQL::Language::Nodes::AbstractNode, nil`) — the previously-visited node, or `nil` if this is the root node. + # + # **Returns** + # + # - `Array, nil` — If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`. + # + # :call-seq: + # #{node_method}(GraphQL::Language::Nodes::AbstractNode node, GraphQL::Language::Nodes::AbstractNode | nil parent) -> Array | nil def #{node_method}(node, parent) if node.equal?(DELETE_NODE) # This might be passed to `super(DELETE_NODE, ...)` diff --git a/lib/graphql/load_application_object_failed_error.rb b/lib/graphql/load_application_object_failed_error.rb index 6546d5ba45b..90ac9264c56 100644 --- a/lib/graphql/load_application_object_failed_error.rb +++ b/lib/graphql/load_application_object_failed_error.rb @@ -4,15 +4,35 @@ 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. + # See [GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader#load_application_object_failed](rdoc-ref: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 + # **Returns** + # + # - `GraphQL::Schema::Argument` — the argument definition for the argument that was looked up + # + # :call-seq: + # argument -> GraphQL::Schema::Argument attr_reader :argument - # @return [String] The ID provided by the client + # **Returns** + # + # - `String` — The ID provided by the client + # + # :call-seq: + # id -> String attr_reader :id - # @return [Object] The value found with this ID + # **Returns** + # + # - `Object` — The value found with this ID + # + # :call-seq: + # object -> Object attr_reader :object - # @return [GraphQL::Query::Context] + # **Returns** + # + # - `GraphQL::Query::Context` + # + # :call-seq: + # context -> GraphQL::Query::Context attr_reader :context def initialize(argument:, id:, object:, context:) diff --git a/lib/graphql/pagination/connection.rb b/lib/graphql/pagination/connection.rb index 32ca4141096..bdfd11cf97a 100644 --- a/lib/graphql/pagination/connection.rb +++ b/lib/graphql/pagination/connection.rb @@ -15,10 +15,20 @@ class Connection class PaginationImplementationMissingError < GraphQL::Error end - # @return [Object] A list object, from the application. This is the unpaginated value passed into the connection. + # **Returns** + # + # - `Object` — A list object, from the application. This is the unpaginated value passed into the connection. + # + # :call-seq: + # items -> Object attr_reader :items - # @return [GraphQL::Query::Context] + # **Returns** + # + # - `GraphQL::Query::Context` + # + # :call-seq: + # context -> GraphQL::Query::Context attr_reader :context def context=(new_ctx) @@ -29,13 +39,23 @@ def context=(new_ctx) @context end - # @return [Object] the object this collection belongs to + # **Returns** + # + # - `Object` — the object this collection belongs to + # + # :call-seq: + # parent -> Object 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`. + # **Returns** + # + # - `String, nil` — the client-provided cursor. `""` is treated as `nil`. + # + # :call-seq: + # before() -> String | nil def before if defined?(@before) @before @@ -44,7 +64,12 @@ def before end end - # @return [String, nil] the client-provided cursor. `""` is treated as `nil`. + # **Returns** + # + # - `String, nil` — the client-provided cursor. `""` is treated as `nil`. + # + # :call-seq: + # after() -> String | nil def after if defined?(@after) @after @@ -53,19 +78,29 @@ def after end end - # @return [Hash Object>] The field arguments from the field that returned this connection + # **Returns** + # + # - `Hash Object>` — The field arguments from the field that returned this connection + # + # :call-seq: + # arguments -> Hash[Symbol, Object] attr_accessor :arguments - # @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 arguments [Hash] The arguments to the field that returned the collection wrapped by this connection - # @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given and no `default_page_size` is set. - # @param default_page_size [Integer, nil] A configured value to determine the result size when neither first or last are given. + # **Parameters** + # + # - `items` (`Object`) — some unpaginated collection item, like an `Array` or `ActiveRecord::Relation` + # - `context` (`Query::Context`) + # - `parent` (`Object`) — The object this collection belongs to + # - `first` (`Integer, nil`) — The limit parameter from the client, if it provided one + # - `after` (`String, nil`) — A cursor for pagination, if the client provided one + # - `last` (`Integer, nil`) — Limit parameter from the client, if provided + # - `before` (`String, nil`) — A cursor for pagination, if the client provided one. + # - `arguments` (`Hash`) — The arguments to the field that returned the collection wrapped by this connection + # - `max_page_size` (`Integer, nil`) — A configured value to cap the result size. Applied as `first` if neither first or last are given and no `default_page_size` is set. + # - `default_page_size` (`Integer, nil`) — A configured value to determine the result size when neither first or last are given. + # + # :call-seq: + # initialize(Object items, Object parent:, field:, Query::Context context:, Integer | nil first:, String | nil after:, Integer | nil max_page_size:, Integer | nil default_page_size:, Integer | nil last:, String | nil before:, edge_class:, Hash arguments:) def initialize(items, parent: nil, field: nil, context: nil, first: nil, after: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, last: nil, before: nil, edge_class: nil, arguments: nil) @items = items @parent = parent @@ -135,13 +170,12 @@ def has_default_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 `default_page_size` is - # present, default_page_size is used for first. If `default_page_size` - # is greater than `max_page_size``, it'll be clamped down to - # `max_page_size`. If `default_page_size` is nil, use `max_page_size`. + # **Returns** + # + # - `Integer, nil` — A clamped `first` value. (The underlying instance variable doesn't have limits on it.) If neither `first` nor `last` is given, but `default_page_size` is present, default_page_size is used for first. If `default_page_size` is greater than `max_page_size``, it'll be clamped down to `max_page_size`. If `default_page_size` is nil, use `max_page_size`. + # + # :call-seq: + # first() -> Integer | nil def first @first ||= begin capped = limit_pagination_argument(@first_value, max_page_size) @@ -155,36 +189,69 @@ def first # This is called by `Relay::RangeAdd` -- it can be overridden # when `item` needs some modifications based on this connection's state. # - # @param item [Object] An item newly added to `items` - # @return [Edge] + # **Parameters** + # + # - `item` (`Object`) — An item newly added to `items` + # + # **Returns** + # + # - `Edge` + # + # :call-seq: + # range_add_edge(Object item) -> Edge def range_add_edge(item) edge_class.new(item, self) end attr_writer :last - # @return [Integer, nil] A clamped `last` value. (The underlying instance variable doesn't have limits on it) + # **Returns** + # + # - `Integer, nil` — A clamped `last` value. (The underlying instance variable doesn't have limits on it) + # + # :call-seq: + # last() -> Integer | nil def last @last ||= limit_pagination_argument(@last_value, max_page_size) end - # @return [Array] {nodes}, but wrapped with Edge instances + # **Returns** + # + # - `Array` — [nodes](rdoc-ref:nodes), but wrapped with Edge instances + # + # :call-seq: + # edges() -> Array[Edge] def edges @edges ||= nodes.map { |n| @edge_class.new(n, self) } end - # @return [Class] A wrapper class for edges of this connection + # **Returns** + # + # - `Class` — A wrapper class for edges of this connection + # + # :call-seq: + # edge_class -> Class attr_accessor :edge_class - # @return [GraphQL::Schema::Field] The field this connection was returned by + # **Returns** + # + # - `GraphQL::Schema::Field` — The field this connection was returned by + # + # :call-seq: + # field -> GraphQL::Schema::Field attr_accessor :field - # @return [Array] A slice of {items}, constrained by {@first_value}/{@after_value}/{@last_value}/{@before_value} + # **Returns** + # + # - `Array` — A slice of [items](rdoc-ref:items), constrained by `@first_value`/`@after_value`/`@last_value`/`@before_value` + # + # :call-seq: + # nodes() -> Array[Object] def nodes raise PaginationImplementationMissingError, "Implement #{self.class}#nodes to paginate `@items`" end - # A dynamic alias for compatibility with {Relay::BaseConnection}. - # @deprecated use {#nodes} instead + # A dynamic alias for compatibility with [GraphQL::Types::Relay::BaseConnection](rdoc-ref:GraphQL::Types::Relay::BaseConnection). + # **Deprecated:** use [nodes](rdoc-ref:#nodes) instead def edge_nodes nodes end @@ -194,29 +261,58 @@ def page_info self end - # @return [Boolean] True if there are more items after this page + # **Returns** + # + # - `Boolean` — True if there are more items after this page + # + # :call-seq: + # has_next_page() -> bool 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 + # **Returns** + # + # - `Boolean` — True if there were items before these items + # + # :call-seq: + # has_previous_page() -> bool 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} + # **Returns** + # + # - `String` — The cursor of the first item in [nodes](rdoc-ref:nodes) + # + # :call-seq: + # start_cursor() -> String def start_cursor nodes.first && cursor_for(nodes.first) end - # @return [String] The cursor of the last item in {nodes} + # **Returns** + # + # - `String` — The cursor of the last item in [nodes](rdoc-ref:nodes) + # + # :call-seq: + # end_cursor() -> String 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] + # + # **Parameters** + # + # - `item` (`Object`) — one of the passed in [items](rdoc-ref:items), taken from [nodes](rdoc-ref:nodes) + # + # **Returns** + # + # - `String` + # + # :call-seq: + # cursor_for(Object item) -> String def cursor_for(item) raise PaginationImplementationMissingError, "Implement #{self.class}#cursor_for(item) to return the cursor for #{item.inspect}" end @@ -233,9 +329,17 @@ def detect_was_authorized_by_scope_items end end - # @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` + # **Parameters** + # + # - `argument` (`nil, Integer`) — `first` or `last`, as provided by the client + # - `max_page_size` (`nil, Integer`) + # + # **Returns** + # + # - `nil, Integer` — `nil` if the input was `nil`, otherwise a value between `0` and `max_page_size` + # + # :call-seq: + # limit_pagination_argument(nil | Integer argument, nil | Integer max_page_size) -> nil | Integer def limit_pagination_argument(argument, max_page_size) if argument if argument < 0 diff --git a/lib/graphql/pagination/connections.rb b/lib/graphql/pagination/connections.rb index dfcb63c3ee0..e7ba26e884d 100644 --- a/lib/graphql/pagination/connections.rb +++ b/lib/graphql/pagination/connections.rb @@ -6,17 +6,25 @@ module Pagination # # Attach as a plugin. # - # @example Adding a custom wrapper - # class MySchema < GraphQL::Schema - # connections.add(MyApp::SearchResults, MyApp::SearchResultsConnection) - # end + # See [Schema.connections](rdoc-ref:Schema.connections) # - # @example Removing default connection support for arrays (they can still be manually wrapped) - # class MySchema < GraphQL::Schema - # connections.delete(Array) - # end + # **Examples** # - # @see {Schema.connections} + # **Example: Adding a custom wrapper** + # + # ```ruby + # class MySchema < GraphQL::Schema + # connections.add(MyApp::SearchResults, MyApp::SearchResultsConnection) + # end + # ``` + # + # **Example: Removing default connection support for arrays (they can still be manually wrapped)** + # + # ```ruby + # class MySchema < GraphQL::Schema + # connections.delete(Array) + # end + # ``` class Connections class ImplementationMissingError < GraphQL::Error end @@ -57,8 +65,7 @@ def wrapper_for(items, wrappers: all_wrappers) end # Used by the runtime to wrap values in connection wrappers. - # @api Private - def wrap(field, parent, items, arguments, context) + def wrap(field, parent, items, arguments, context) # :nodoc: return items if GraphQL::Execution::Interpreter::RawValue === items wrappers = context ? context.namespace(:connections)[:all_wrappers] : all_wrappers impl = wrapper_for(items, wrappers: wrappers) @@ -114,8 +121,7 @@ def populate_connection(field, object, value, original_arguments, context) end end # use an override if there is one - # @api private - def edge_class_for_field(field) + def edge_class_for_field(field) # :nodoc: 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 != Pagination::Connection::Edge diff --git a/lib/graphql/pagination/relation_connection.rb b/lib/graphql/pagination/relation_connection.rb index 0e6b091474f..85181804570 100644 --- a/lib/graphql/pagination/relation_connection.rb +++ b/lib/graphql/pagination/relation_connection.rb @@ -53,39 +53,84 @@ def cursor_for(item) private - # @param relation [Object] A database query object - # @param _initial_offset [Integer] The number of items already excluded from the relation - # @param size [Integer] The value against which we check the relation size - # @return [Boolean] True if the number of items in this relation is larger than `size` + # **Parameters** + # + # - `relation` (`Object`) — A database query object + # - `_initial_offset` (`Integer`) — The number of items already excluded from the relation + # - `size` (`Integer`) — The value against which we check the relation size + # + # **Returns** + # + # - `Boolean` — True if the number of items in this relation is larger than `size` + # + # :call-seq: + # relation_larger_than(Object relation, Integer _initial_offset, Integer size) -> bool def relation_larger_than(relation, _initial_offset, size) relation_count(set_limit(relation, size + 1)) == size + 1 end - # @param relation [Object] A database query object - # @return [Integer, nil] The offset value, or nil if there isn't one + # **Parameters** + # + # - `relation` (`Object`) — A database query object + # + # **Returns** + # + # - `Integer, nil` — The offset value, or nil if there isn't one + # + # :call-seq: + # relation_offset(Object relation) -> Integer | nil 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 + # **Parameters** + # + # - `relation` (`Object`) — A database query object + # + # **Returns** + # + # - `Integer, nil` — The limit value, or nil if there isn't one + # + # :call-seq: + # relation_limit(Object relation) -> Integer | nil 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!) + # **Parameters** + # + # - `relation` (`Object`) — A database query object + # + # **Returns** + # + # - `Integer, nil` — The number of items in this relation (hopefully determined without loading all records into memory!) + # + # :call-seq: + # relation_count(Object relation) -> Integer | nil 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 + # **Parameters** + # + # - `relation` (`Object`) — A database query object + # + # **Returns** + # + # - `Object` — A modified query object which will return no records + # + # :call-seq: + # null_relation(Object relation) -> Object def null_relation(relation) raise "#{self.class}#null_relation(relation) must return an empty relation for a #{relation.class} (#{relation.inspect})" end - # @return [Integer] + # **Returns** + # + # - `Integer` + # + # :call-seq: + # offset_from_cursor(cursor) -> Integer def offset_from_cursor(cursor) decode(cursor).to_i end @@ -165,12 +210,22 @@ def sliced_nodes end end - # @return [Integer, nil] + # **Returns** + # + # - `Integer, nil` + # + # :call-seq: + # before_offset() -> Integer | nil def before_offset @before_offset ||= before && offset_from_cursor(before) end - # @return [Integer, nil] + # **Returns** + # + # - `Integer, nil` + # + # :call-seq: + # after_offset() -> Integer | nil def after_offset @after_offset ||= after && offset_from_cursor(after) end diff --git a/lib/graphql/query.rb b/lib/graphql/query.rb index 2057270fb91..0e2d28ca8c1 100644 --- a/lib/graphql/query.rb +++ b/lib/graphql/query.rb @@ -2,6 +2,41 @@ module GraphQL # A combination of query string and {Schema} instance which can be reduced to a {#result}. + # + # The API-specific portions of `guides/queries/executing_queries.md` were + # migrated to `Schema#execute`, `Schema#multiplex`, and this initializer. + # Those APIs accept query strings or parsed documents, variables, context, + # root values, operation names, validation controls, and depth/complexity + # limits. The guide remains a standalone execution tutorial. + # + # ## API-specific portions + # + # [GraphQL::Schema.execute](rdoc-ref:GraphQL::Schema.execute) runs one query + # and [GraphQL::Schema.multiplex](rdoc-ref:GraphQL::Schema.multiplex) runs + # several queries with shared multiplex-level context. Both delegate to this + # class, so the following options have the same meaning in each API: + # + # - `query` or the positional query string is a GraphQL document to parse. + # - `document:` accepts an already-parsed + # [GraphQL::Language::Nodes::Document](rdoc-ref:GraphQL::Language::Nodes::Document); + # pass one or the other, not both. + # - `variables:` is a `Hash` of values for `$`-named variables. Keys omit the + # leading `$`; values may contain nested input objects. + # - `context:` contains application values made available to field resolvers, + # schema hooks, and [Query::Context](rdoc-ref:GraphQL::Query::Context). + # - `root_value:` is passed as `obj` to root-level fields. + # - `operation_name:` selects which named operation to execute when a document + # contains more than one operation. + # - `validate: false` skips static validation. Use this only when the document + # has already been validated; execution of an invalid document is undefined. + # - `max_depth:` and `max_complexity:` override the corresponding schema limits + # for this query. `nil` uses the schema configuration. + # + # The [executing queries guide](/queries/executing_queries) keeps examples for + # variables, context, scoped context, and root values. This class and the + # `Schema#execute`/`Schema#multiplex` method comments are the source of truth + # for option names, defaults, and execution behavior. + # migrated from guides/queries/executing_queries.md class Query extend Autoload include Tracing::Traceable @@ -32,10 +67,19 @@ def after_lazy(value, &block) 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}] + # + # **Parameters** + # + # - `ast_node` (`GraphQL::Language::Nodes::AbstractNode`) + # - `definition` (`GraphQL::Schema::Field`) + # - `parent_object` (`GraphQL::Schema::Object`) + # + # **Returns** + # + # - `Hash{Symbol => Object}` + # + # :call-seq: + # arguments_for(GraphQL::Language::Nodes::AbstractNode ast_node, GraphQL::Schema::Field definition, GraphQL::Schema::Object parent_object:) -> Hash[Symbol, Object] def arguments_for(ast_node, definition, parent_object: nil) arguments_cache.fetch(ast_node, definition, parent_object) end @@ -44,8 +88,7 @@ def arguments_cache @arguments_cache ||= Execution::Interpreter::ArgumentsCache.new(self) end - # @api private - def handle_or_reraise(err, **kwargs) + def handle_or_reraise(err, **kwargs) # :nodoc: @schema.handle_or_reraise(context, err, **kwargs) end end @@ -67,13 +110,28 @@ def initialize(name) # 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. + # **Returns** + # + # - `nil, String` — The operation name provided by client or the one inferred from the document. Used to determine which operation to run. + # + # :call-seq: + # operation_name -> nil | String attr_accessor :operation_name - # @return [Boolean] if false, static validation is skipped (execution behavior for invalid queries is undefined) + # **Returns** + # + # - `Boolean` — if false, static validation is skipped (execution behavior for invalid queries is undefined) + # + # :call-seq: + # validate -> bool attr_reader :validate - # @param new_validate [Boolean] if false, static validation is skipped. This can't be reasssigned after validation. + # **Parameters** + # + # - `new_validate` (`Boolean`) — if false, static validation is skipped. This can't be reasssigned after validation. + # + # :call-seq: + # validate=(bool new_validate) def validate=(new_validate) if defined?(@validation_pipeline) && @validation_pipeline && @validation_pipeline.has_validated? raise ArgumentError, "Can't reassign Query#validate= after validation has run, remove this assignment." @@ -82,10 +140,20 @@ def validate=(new_validate) end end - # @return [GraphQL::StaticValidation::Validator] if present, the query will validate with these rules. + # **Returns** + # + # - `GraphQL::StaticValidation::Validator` — if present, the query will validate with these rules. + # + # :call-seq: + # static_validator -> GraphQL::StaticValidation::Validator attr_reader :static_validator - # @param new_validator [GraphQL::StaticValidation::Validator] if present, the query will validate with these rules. This can't be reasssigned after validation. + # **Parameters** + # + # - `new_validator` (`GraphQL::StaticValidation::Validator`) — if present, the query will validate with these rules. This can't be reasssigned after validation. + # + # :call-seq: + # static_validator=(GraphQL::StaticValidation::Validator new_validator) def static_validator=(new_validator) if defined?(@validation_pipeline) && @validation_pipeline && @validation_pipeline.has_validated? raise ArgumentError, "Can't reassign Query#static_validator= after validation has run, remove this assignment." @@ -98,7 +166,12 @@ def static_validator=(new_validator) attr_writer :query_string - # @return [GraphQL::Language::Nodes::Document] + # **Returns** + # + # - `GraphQL::Language::Nodes::Document` + # + # :call-seq: + # document() -> GraphQL::Language::Nodes::Document def document # It's ok if this hasn't been assigned yet if @query_string || @document @@ -112,27 +185,43 @@ def inspect "query ..." end - # @return [String, nil] The name of the operation to run (may be inferred) + # **Returns** + # + # - `String, nil` — The name of the operation to run (may be inferred) + # + # :call-seq: + # selected_operation_name() -> String | nil 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 + # **Returns** + # + # - `String, nil` — the triggered event, if this query is a subscription update + # + # :call-seq: + # subscription_topic -> String | nil 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 visibility_profile [Symbol] Another way to assign `context[:visibility_profile]` + # + # **Parameters** + # + # - `schema` (`GraphQL::Schema`) + # - `query_string` (`String`) + # - `context` (`#[]`) — an arbitrary hash of values which you can access in [Schema::Field#resolve](rdoc-ref:GraphQL::Schema::Field#resolve) + # - `variables` (`Hash`) — values for `$variables` in the query + # - `operation_name` (`String`) — if the query string contains many operations, this is the one which should be executed + # - `root_value` (`Object`) — the object used to resolve fields on the root type + # - `max_depth` (`Numeric`) — the maximum number of nested selections allowed for this query (falls back to schema-level value) + # - `max_complexity` (`Numeric`) — the maximum field complexity for this query (falls back to schema-level value) + # - `visibility_profile` (`Symbol`) — Another way to assign `context[:visibility_profile]` + # + # :call-seq: + # initialize(GraphQL::Schema schema, String query_string, query:, document:, #[] context:, Hash variables:, multiplex:, validate:, static_validator:, Symbol visibility_profile:, subscription_topic:, String operation_name:, Object root_value:, Numeric max_depth:, Numeric max_complexity:, warden:, use_visibility_profile:) def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, multiplex: nil, validate: true, static_validator: nil, visibility_profile: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_visibility_profile: nil) # Even if `variables: nil` is passed, use an empty hash for simpler logic variables ||= {} @@ -216,12 +305,22 @@ def query_string @query_string ||= (document ? document.to_query_string : nil) end - # @return [Symbol, nil] + # **Returns** + # + # - `Symbol, nil` + # + # :call-seq: + # visibility_profile -> Symbol | nil attr_reader :visibility_profile attr_accessor :multiplex - # @return [GraphQL::Tracing::Trace] + # **Returns** + # + # - `GraphQL::Tracing::Trace` + # + # :call-seq: + # current_trace() -> GraphQL::Tracing::Trace def current_trace @current_trace ||= context[:trace] || (multiplex ? multiplex.current_trace : schema.new_trace(multiplex: multiplex, query: self)) end @@ -231,7 +330,13 @@ def subscription_update? end # A lookahead for the root selections of this query - # @return [GraphQL::Execution::Lookahead] + # + # **Returns** + # + # - `GraphQL::Execution::Lookahead` + # + # :call-seq: + # lookahead() -> GraphQL::Execution::Lookahead def lookahead @lookahead ||= begin if selected_operation.nil? @@ -242,8 +347,7 @@ def lookahead end end - # @api private - def result_values=(result_hash) + def result_values=(result_hash) # :nodoc: if @executed raise "Invariant: Can't reassign result" else @@ -252,8 +356,7 @@ def result_values=(result_hash) end end - # @api private - attr_reader :result_values + attr_reader :result_values # :nodoc: def fragments with_prepared_ast { @fragments } @@ -272,8 +375,17 @@ def path # where the path references a field in the AST and the object will be treated # as the return value from that field. Subfields of the field named by `path` # will be executed with `object` as the starting point - # @param partials_hashes [Array Object}>] Hashes with `path:` and `object:` keys - # @return [Array] + # + # **Parameters** + # + # - `partials_hashes` (`Array Object}>`) — Hashes with `path:` and `object:` keys + # + # **Returns** + # + # - `Array` + # + # :call-seq: + # run_partials(Array[Hash[Symbol, Object]] partials_hashes) -> Array[GraphQL::Query::Result] def run_partials(partials_hashes) partials = partials_hashes.map { |partial_options| Partial.new(query: self, **partial_options) } if context[:__graphql_execute_next] @@ -284,7 +396,13 @@ def run_partials(partials_hashes) end # Get the result for this query, executing it once - # @return [GraphQL::Query::Result] A Hash-like GraphQL response, with `"data"` and/or `"errors"` keys + # + # **Returns** + # + # - `GraphQL::Query::Result` — A Hash-like GraphQL response, with `"data"` and/or `"errors"` keys + # + # :call-seq: + # result() -> GraphQL::Query::Result def result if !@executed Execution::Interpreter.run_all(@schema, [self], context: @context) @@ -302,7 +420,13 @@ def static_errors # 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] + # + # **Returns** + # + # - `GraphQL::Language::Nodes::OperationDefinition, nil` + # + # :call-seq: + # selected_operation() -> GraphQL::Language::Nodes::OperationDefinition | nil def selected_operation with_prepared_ast { @selected_operation } end @@ -310,9 +434,14 @@ def selected_operation # 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}. + # If some variable is invalid, errors are added to the query's validation errors. + # + # **Returns** # - # @return [GraphQL::Query::Variables] Variables to apply to this query + # - `GraphQL::Query::Variables` — Variables to apply to this query + # + # :call-seq: + # variables() -> GraphQL::Query::Variables def variables @variables ||= begin with_prepared_ast { @@ -328,7 +457,13 @@ def variables # 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. + # + # **Returns** + # + # - `String, nil` — Returns nil if the query is invalid. + # + # :call-seq: + # sanitized_query_string(inline_variables:) -> String | nil def sanitized_query_string(inline_variables: true) with_prepared_ast { schema.sanitized_printer.new(self, inline_variables: inline_variables).sanitized_query_string @@ -344,19 +479,34 @@ def sanitized_query_string(inline_variables: true) # # 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 + # See `operation_fingerprint` and `variables_fingerprint` for the components. + # + # **Returns** + # + # - `String` — An opaque hash identifying this operation-variables combination + # + # :call-seq: + # fingerprint() -> String def fingerprint @fingerprint ||= "#{operation_fingerprint}/#{variables_fingerprint}" end - # @return [String] An opaque hash for identifying this query's given query string and selected operation + # **Returns** + # + # - `String` — An opaque hash for identifying this query's given query string and selected operation + # + # :call-seq: + # operation_fingerprint() -> String 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) + # **Returns** + # + # - `String` — An opaque hash for identifying this query's given a variable values (not including defaults) + # + # :call-seq: + # variables_fingerprint() -> String def variables_fingerprint @variables_fingerprint ||= "#{provided_variables.size}/#{Fingerprint.generate(provided_variables.to_json)}" end @@ -410,10 +560,19 @@ def types @visibility_profile || warden.visibility_profile end - # @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` + # See [possible_types](rdoc-ref:#possible_types) to apply filtering from `only` / `except` + # + # **Parameters** + # + # - `abstract_type` (`GraphQL::UnionType, GraphQL::InterfaceType`) + # - `value` (`Object`) — Any runtime value + # + # **Returns** + # + # - `GraphQL::ObjectType, nil` — The runtime type of `value` from [Schema.resolve_type](rdoc-ref:GraphQL::Schema::resolve_type) + # + # :call-seq: + # resolve_type(GraphQL::UnionType | GraphQL::InterfaceType abstract_type, Object value) -> GraphQL::ObjectType | nil def resolve_type(abstract_type, value = NOT_CONFIGURED) if value.is_a?(Symbol) && value == NOT_CONFIGURED # Old method signature diff --git a/lib/graphql/query/context.rb b/lib/graphql/query/context.rb index 3896b714263..68bab73f2f5 100644 --- a/lib/graphql/query/context.rb +++ b/lib/graphql/query/context.rb @@ -31,18 +31,39 @@ def add(err_or_msg) extend Forwardable include Schema::Member::HasDataloader - # @return [Array] errors returned during execution + # **Returns** + # + # - `Array` — errors returned during execution + # + # :call-seq: + # errors -> Array[GraphQL::ExecutionError] attr_reader :errors - # @return [GraphQL::Query] The query whose context this is + # **Returns** + # + # - `GraphQL::Query` — The query whose context this is + # + # :call-seq: + # query -> GraphQL::Query attr_reader :query - # @return [GraphQL::Schema] + # **Returns** + # + # - `GraphQL::Schema` + # + # :call-seq: + # schema -> GraphQL::Schema attr_reader :schema # 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 + # + # **Parameters** + # + # - `query` (`GraphQL::Query`) — the query who owns this context + # - `values` (`Hash`) — A hash of arbitrary values which will be accessible at query-time + # + # :call-seq: + # initialize(GraphQL::Query query:, schema:, Hash values:) def initialize(query:, schema: query.schema, values:) @query = query @schema = schema @@ -55,7 +76,13 @@ def initialize(query:, schema: query.schema, values:) end # Modify this hash to return extensions to client. - # @return [Hash] A hash that will be added verbatim to the result hash, as `"extensions" => { ... }` + # + # **Returns** + # + # - `Hash` — A hash that will be added verbatim to the result hash, as `"extensions" => { ... }` + # + # :call-seq: + # response_extensions() -> Hash def response_extensions namespace(:__query_result_extensions__) end @@ -64,14 +91,11 @@ def dataloader @dataloader ||= self[:dataloader] || (query.multiplex ? query.multiplex.dataloader : schema.dataloader_class.new) end - # @api private - attr_writer :interpreter + attr_writer :interpreter # :nodoc: - # @api private - attr_writer :value + attr_writer :value # :nodoc: - # @api private - attr_reader :scoped_context + attr_reader :scoped_context # :nodoc: def []=(key, value) @provided_values[key] = value @@ -86,8 +110,7 @@ def types attr_writer :types RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path]).freeze - # @!method []=(key, value) - # Reassign `key` to the hash passed to {Schema#execute} as `context:` + # **Method:** `[]=(key, value)` — Reassign `key` to the hash passed to [Schema#execute](rdoc-ref:Schema#execute) as `context:` # Lookup `key` from the hash passed to {Schema#execute} as `context:` def [](key) @@ -116,8 +139,17 @@ def skip end # Add error at query-level. - # @param error [GraphQL::ExecutionError] an execution error - # @return [void] + # + # **Parameters** + # + # - `error` (`GraphQL::ExecutionError`) — an execution error + # + # **Returns** + # + # - `void` + # + # :call-seq: + # add_error(GraphQL::ExecutionError error) -> void def add_error(error) if !error.is_a?(GraphQL::RuntimeError) raise TypeError, "expected error to be a GraphQL::RuntimeError, but was #{error.class}" @@ -126,16 +158,34 @@ def add_error(error) nil end - # @param value [Object] Any object to be inserted directly into the final response - # @return [GraphQL::Execution::Interpreter::RawValue] Return this from the field + # **Parameters** + # + # - `value` (`Object`) — Any object to be inserted directly into the final response + # + # **Returns** + # + # - `GraphQL::Execution::Interpreter::RawValue` — Return this from the field + # + # :call-seq: + # raw_value(Object value) -> GraphQL::Execution::Interpreter::RawValue def raw_value(value) GraphQL::Execution::Interpreter::RawValue.new(value) end - # @example Print the GraphQL backtrace during field resolution - # puts ctx.backtrace + # **Examples** + # + # **Example: Print the GraphQL backtrace during field resolution** + # + # ```ruby + # puts ctx.backtrace + # ``` + # + # **Returns** + # + # - `GraphQL::Backtrace` — The backtrace for this point in query execution # - # @return [GraphQL::Backtrace] The backtrace for this point in query execution + # :call-seq: + # backtrace() -> GraphQL::Backtrace def backtrace GraphQL::Backtrace.new(self) end @@ -217,17 +267,30 @@ def key?(key) @scoped_context.key?(key) || @provided_values.key?(key) end - # @return [GraphQL::Schema::Warden] + # **Returns** + # + # - `GraphQL::Schema::Warden` + # + # :call-seq: + # warden() -> GraphQL::Schema::Warden def warden @warden ||= (@query && @query.warden) end - # @api private - attr_writer :warden + attr_writer :warden # :nodoc: # Get an isolated hash for `ns`. Doesn't affect user-provided storage. - # @param ns [Object] a usage-specific namespace identifier - # @return [Hash] namespaced storage + # + # **Parameters** + # + # - `ns` (`Object`) — a usage-specific namespace identifier + # + # **Returns** + # + # - `Hash` — namespaced storage + # + # :call-seq: + # namespace(Object ns) -> Hash def namespace(ns) if ns == :interpreter self @@ -236,7 +299,12 @@ def namespace(ns) end end - # @return [Boolean] true if this namespace was accessed before + # **Returns** + # + # - `Boolean` — true if this namespace was accessed before + # + # :call-seq: + # namespace?(ns) -> bool def namespace?(ns) @storage.key?(ns) end @@ -261,13 +329,24 @@ def scoped_set!(key, value) # Use this when you need to do a scoped set _inside_ a lazy-loaded (or batch-loaded) # block of code. # - # @example using scoped context inside a promise - # scoped_ctx = context.scoped - # SomeBatchLoader.load(...).then do |thing| - # # use a scoped_ctx which was created _before_ dataloading: - # scoped_ctx.set!(:thing, thing) - # end - # @return [Context::Scoped] + # **Examples** + # + # **Example: using scoped context inside a promise** + # + # ```ruby + # scoped_ctx = context.scoped + # SomeBatchLoader.load(...).then do |thing| + # # use a scoped_ctx which was created _before_ dataloading: + # scoped_ctx.set!(:thing, thing) + # end + # ``` + # + # **Returns** + # + # - `Context::Scoped` + # + # :call-seq: + # scoped() -> Context::Scoped def scoped Scoped.new(@scoped_context, current_path) end diff --git a/lib/graphql/query/fingerprint.rb b/lib/graphql/query/fingerprint.rb index 8757d85984c..009ebfdae60 100644 --- a/lib/graphql/query/fingerprint.rb +++ b/lib/graphql/query/fingerprint.rb @@ -4,14 +4,21 @@ module GraphQL class Query - # @api private - # @see Query#query_fingerprint - # @see Query#variables_fingerprint - # @see Query#fingerprint - module Fingerprint + # The resulting hashes are exposed by `Query#operation_fingerprint`, + # `Query#variables_fingerprint`, and `Query#fingerprint`. + module Fingerprint # :nodoc: # Make an obfuscated hash of the given string (either a query string or variables JSON) - # @param string [String] - # @return [String] A normalized, opaque hash + # + # **Parameters** + # + # - `string` (`String`) + # + # **Returns** + # + # - `String` — A normalized, opaque hash + # + # :call-seq: + # generate(input_str) -> String def self.generate(input_str) # Implemented to be: # - Short (and uniform) length diff --git a/lib/graphql/query/partial.rb b/lib/graphql/query/partial.rb index 6e4c0e17555..dc30543206b 100644 --- a/lib/graphql/query/partial.rb +++ b/lib/graphql/query/partial.rb @@ -1,24 +1,29 @@ # frozen_string_literal: true module GraphQL class Query - # This class is _like_ a {GraphQL::Query}, except it can run on an arbitrary path within a query string. + # This class is _like_ a [GraphQL::Query](rdoc-ref:GraphQL::Query), except it can run on an arbitrary path within a query string. # - # It depends on a "parent" {Query}. + # It depends on a "parent" [Query](rdoc-ref:Query). # # During execution, it calls query-related tracing hooks but passes itself as `query:`. # - # The {Partial} will use your {Schema.resolve_type} hook to find the right GraphQL type to use for + # The [Partial](rdoc-ref:Partial) will use your [Schema.resolve_type](rdoc-ref:Schema.resolve_type) hook to find the right GraphQL type to use for # `object` in some cases. # - # @see Query#run_partials Run via {Query#run_partials} + # See [Query#run_partials](rdoc-ref:Query#run_partials) Run via [Query#run_partials](rdoc-ref:Query#run_partials) class Partial include Query::Runnable - # @param path [Array] A path in `query.query_string` to start executing from - # @param object [Object] A starting object for execution - # @param query [GraphQL::Query] A full query instance that this partial is based on. Caches are shared. - # @param context [Hash] Extra context values to merge into `query.context`, if provided - # @param fragment_node [GraphQL::Language::Nodes::InlineFragment, GraphQL::Language::Nodes::FragmentDefinition] + # **Parameters** + # + # - `path` (`Array`) — A path in `query.query_string` to start executing from + # - `object` (`Object`) — A starting object for execution + # - `query` (`GraphQL::Query`) — A full query instance that this partial is based on. Caches are shared. + # - `context` (`Hash`) — Extra context values to merge into `query.context`, if provided + # - `fragment_node` (`GraphQL::Language::Nodes::InlineFragment, GraphQL::Language::Nodes::FragmentDefinition`) + # + # :call-seq: + # initialize(Array[String | Integer] path:, Object object:, GraphQL::Query query:, Hash context:, GraphQL::Language::Nodes::InlineFragment | GraphQL::Language::Nodes::FragmentDefinition fragment_node:, type:) def initialize(path: nil, object:, query:, context: nil, fragment_node: nil, type: nil) @path = path @object = object @@ -65,7 +70,12 @@ def path @query.path end - # @return [GraphQL::Query::Partial] + # **Returns** + # + # - `GraphQL::Query::Partial` + # + # :call-seq: + # partial() -> GraphQL::Query::Partial def partial @query end diff --git a/lib/graphql/query/result.rb b/lib/graphql/query/result.rb index 92ce480b178..92f4d903639 100644 --- a/lib/graphql/query/result.rb +++ b/lib/graphql/query/result.rb @@ -13,10 +13,20 @@ def initialize(query:, values:) @to_h = values end - # @return [GraphQL::Query] The query that was executed + # **Returns** + # + # - `GraphQL::Query` — The query that was executed + # + # :call-seq: + # query -> GraphQL::Query attr_reader :query - # @return [Hash] The resulting hash of "data" and/or "errors" + # **Returns** + # + # - `Hash` — The resulting hash of "data" and/or "errors" + # + # :call-seq: + # to_h -> Hash attr_reader :to_h def_delegators :@query, :context, :mutation?, :query?, :subscription? @@ -47,7 +57,12 @@ def inspect # # (The query is ignored for comparing result equality.) # - # @return [Boolean] + # **Returns** + # + # - `Boolean` + # + # :call-seq: + # ==(other) -> bool def ==(other) case other when Hash diff --git a/lib/graphql/query/validation_pipeline.rb b/lib/graphql/query/validation_pipeline.rb index 58dc7142b1a..41c71c5e949 100644 --- a/lib/graphql/query/validation_pipeline.rb +++ b/lib/graphql/query/validation_pipeline.rb @@ -3,17 +3,16 @@ module GraphQL class Query # Contain the validation pipeline and expose the results. # - # 0. Checks in {Query#initialize}: + # 0. Checks in the [GraphQL::Query](rdoc-ref:GraphQL::Query) constructor: # - 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. + # [valid?](rdoc-ref:#valid?) is false if any of the above checks halted the pipeline. # - # @api private - class ValidationPipeline + class ValidationPipeline # :nodoc: attr_reader :max_depth, :max_complexity, :validate_timeout_remaining def initialize(query:, parse_error:, operation_name_error:, max_depth:, max_complexity:) @@ -28,13 +27,23 @@ def initialize(query:, parse_error:, operation_name_error:, max_depth:, max_comp @has_validated = false end - # @return [Boolean] does this query have errors that should prevent it from running? + # **Returns** + # + # - `Boolean` — does this query have errors that should prevent it from running? + # + # :call-seq: + # valid?() -> bool def valid? ensure_has_validated @valid end - # @return [Array] Static validation errors for the query string + # **Returns** + # + # - `Array` — Static validation errors for the query string + # + # :call-seq: + # validation_errors() -> Array[GraphQL::StaticValidation::Error | GraphQL::Query::VariableValidationError] def validation_errors ensure_has_validated @validation_errors diff --git a/lib/graphql/query/variables.rb b/lib/graphql/query/variables.rb index 4b2c12e0918..3756d449325 100644 --- a/lib/graphql/query/variables.rb +++ b/lib/graphql/query/variables.rb @@ -5,7 +5,12 @@ class Query class Variables extend Forwardable - # @return [Array] Any errors encountered when parsing the provided variables and literal values + # **Returns** + # + # - `Array` — Any errors encountered when parsing the provided variables and literal values + # + # :call-seq: + # errors -> Array[GraphQL::Query::VariableValidationError] attr_reader :errors attr_reader :context diff --git a/lib/graphql/railtie.rb b/lib/graphql/railtie.rb index b99b50f0576..9b4b9b2bed4 100644 --- a/lib/graphql/railtie.rb +++ b/lib/graphql/railtie.rb @@ -1,12 +1,15 @@ # frozen_string_literal: true module GraphQL - # Support {GraphQL::Parser::Cache} and {GraphQL.eager_load!} + # Support [GraphQL::Language::Cache](rdoc-ref:GraphQL::Language::Cache) and [GraphQL.eager_load!](rdoc-ref:GraphQL.eager_load!) # - # @example Enable the parser cache with default directory + # **Examples** # - # config.graphql.parser_cache = true + # **Example: Enable the parser cache with default directory** # + # ```ruby + # config.graphql.parser_cache = true + # ``` class Railtie < Rails::Railtie config.graphql = ActiveSupport::OrderedOptions.new config.graphql.parser_cache = false diff --git a/lib/graphql/rake_task.rb b/lib/graphql/rake_task.rb index e5601843b92..7611b2cd857 100644 --- a/lib/graphql/rake_task.rb +++ b/lib/graphql/rake_task.rb @@ -11,21 +11,32 @@ module GraphQL # # Use `load_context:` and `visible?` to dump schemas under certain visibility constraints. # - # @example Dump a Schema to .graphql + .json files - # require "graphql/rake_task" - # GraphQL::RakeTask.new(schema_name: "MySchema") + # **Examples** # - # # $ rake graphql:schema:dump - # # Schema IDL dumped to ./schema.graphql - # # Schema JSON dumped to ./schema.json + # **Example: Dump a Schema to .graphql + .json files** # - # @example Invoking the task from Ruby - # require "rake" - # Rake::Task["graphql:schema:dump"].invoke + # ```ruby + # require "graphql/rake_task" + # GraphQL::RakeTask.new(schema_name: "MySchema") # - # @example Providing arguments to build the introspection query - # require "graphql/rake_task" - # GraphQL::RakeTask.new(schema_name: "MySchema", include_is_one_of: true) + # # $ rake graphql:schema:dump + # # Schema IDL dumped to ./schema.graphql + # # Schema JSON dumped to ./schema.json + # ``` + # + # **Example: Invoking the task from Ruby** + # + # ```ruby + # require "rake" + # Rake::Task["graphql:schema:dump"].invoke + # ``` + # + # **Example: Providing arguments to build the introspection query** + # + # ```ruby + # require "graphql/rake_task" + # GraphQL::RakeTask.new(schema_name: "MySchema", include_is_one_of: true) + # ``` class RakeTask include Rake::DSL @@ -45,37 +56,84 @@ class RakeTask include_is_one_of: false } - # @return [String] Namespace for generated tasks + # **Returns** + # + # - `String` — Namespace for generated tasks + # + # :call-seq: + # namespace -> String attr_writer :namespace def rake_namespace @namespace end - # @return [Array] + # **Returns** + # + # - `Array` + # + # :call-seq: + # dependencies -> Array[String] attr_accessor :dependencies - # @return [String] By default, used to find the schema as a constant. - # @see {#load_schema} for loading a schema another way + # See [load_schema](rdoc-ref:#load_schema) for loading a schema another way + # + # **Returns** + # + # - `String` — By default, used to find the schema as a constant. + # + # :call-seq: + # schema_name -> String attr_accessor :schema_name - # @return [<#call(task)>] A proc for loading the target GraphQL schema + # **Returns** + # + # - `<#call(task)>` — A proc for loading the target GraphQL schema + # + # :call-seq: + # load_schema -> #call(task) attr_accessor :load_schema - # @return [<#call(task)>] A callable for loading the query context + # **Returns** + # + # - `<#call(task)>` — A callable for loading the query context + # + # :call-seq: + # load_context -> #call(task) attr_accessor :load_context - # @return [String] target for IDL task + # **Returns** + # + # - `String` — target for IDL task + # + # :call-seq: + # idl_outfile -> String attr_accessor :idl_outfile - # @return [String] target for JSON task + # **Returns** + # + # - `String` — target for JSON task + # + # :call-seq: + # json_outfile -> String attr_accessor :json_outfile - # @return [String] directory for IDL & JSON files + # **Returns** + # + # - `String` — directory for IDL & JSON files + # + # :call-seq: + # directory -> String attr_accessor :directory - # @return [Boolean] Options for additional fields in the introspection query JSON response - # @see GraphQL::Schema.as_json + # See [GraphQL::Schema.as_json](rdoc-ref:GraphQL::Schema.as_json) GraphQL::Schema.as_json + # + # **Returns** + # + # - `Boolean` — Options for additional fields in the introspection query JSON response + # + # :call-seq: + # include_deprecated_args -> bool attr_accessor :include_deprecated_args, :include_schema_description, :include_is_repeatable, :include_specified_by_url, :include_is_one_of # Set the parameters of this task by passing keyword arguments diff --git a/lib/graphql/relay/range_add.rb b/lib/graphql/relay/range_add.rb index 13358fb670e..3207f78b059 100644 --- a/lib/graphql/relay/range_add.rb +++ b/lib/graphql/relay/range_add.rb @@ -8,32 +8,42 @@ module Relay # 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[:post_id]) - # comments = post.comments - # new_comment = comments.build(body: args[:body]) - # new_comment.save! + # **Examples** # - # range_add = GraphQL::Relay::RangeAdd.new( - # parent: post, - # collection: comments, - # item: new_comment, - # context: context, - # ) + # **Example: Adding a comment to list of comments** # - # response = { - # post: post, - # comments_connection: range_add.connection, - # new_comment_edge: range_add.edge, - # } + # ```ruby + # post = Post.find(args[:post_id]) + # 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: context, + # ) + # + # response = { + # post: post, + # comments_connection: range_add.connection, + # new_comment_edge: 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 context [GraphQL::Query::Context] The surrounding `ctx`, will be passed to the connection - # @param parent [Object] The owner of `collection`, will be passed to the connection if provided - # @param edge_class [Class] The class to wrap `item` with (defaults to the connection's edge class) + # **Parameters** + # + # - `collection` (`Object`) — The list of items to wrap in a connection + # - `item` (`Object`) — The newly-added item (will be wrapped in `edge_class`) + # - `context` (`GraphQL::Query::Context`) — The surrounding `ctx`, will be passed to the connection + # - `parent` (`Object`) — The owner of `collection`, will be passed to the connection if provided + # - `edge_class` (`Class`) — The class to wrap `item` with (defaults to the connection's edge class) + # + # :call-seq: + # initialize(Object collection:, Object item:, GraphQL::Query::Context context:, Object parent:, Class edge_class:) def initialize(collection:, item:, context:, parent: nil, edge_class: nil) conn_class = context.schema.connections.wrapper_for(collection) # The rest will be added by ConnectionExtension diff --git a/lib/graphql/rubocop/graphql/default_null_true.rb b/lib/graphql/rubocop/graphql/default_null_true.rb index edfc641bf38..464634ec576 100644 --- a/lib/graphql/rubocop/graphql/default_null_true.rb +++ b/lib/graphql/rubocop/graphql/default_null_true.rb @@ -11,15 +11,17 @@ module GraphQL # to non-null fields (`null: false`) without a breaking change. (The opposite change, from `null: false` # to `null: true`, change.) # - # @example - # # Both of these define `name: String` in GraphQL: + # **Examples** # - # # bad - # field :name, String, null: true + # **Example: # Both of these define `name: String` in GraphQL:** # - # # good - # field :name, String + # ```ruby + # # bad + # field :name, String, null: true # + # # good + # field :name, String + # ``` class DefaultNullTrue < BaseCop MSG = "`null: true` is the default and can be removed." diff --git a/lib/graphql/rubocop/graphql/default_required_true.rb b/lib/graphql/rubocop/graphql/default_required_true.rb index d3ba15a9018..f21e9c982dd 100644 --- a/lib/graphql/rubocop/graphql/default_required_true.rb +++ b/lib/graphql/rubocop/graphql/default_required_true.rb @@ -11,15 +11,17 @@ module GraphQL # to optional arguments (`required: false`) without a breaking change. (The opposite change, from `required: false` # to `required: true`, change.) # - # @example - # # Both of these define `id: ID!` in GraphQL: + # **Examples** # - # # bad - # argument :id, ID, required: true + # **Example: # Both of these define `id: ID!` in GraphQL:** # - # # good - # argument :id, ID + # ```ruby + # # bad + # argument :id, ID, required: true # + # # good + # argument :id, ID + # ``` class DefaultRequiredTrue < BaseCop MSG = "`required: true` is the default and can be removed." diff --git a/lib/graphql/rubocop/graphql/field_type_in_block.rb b/lib/graphql/rubocop/graphql/field_type_in_block.rb index d98838a6d7b..e750b1436e2 100644 --- a/lib/graphql/rubocop/graphql/field_type_in_block.rb +++ b/lib/graphql/rubocop/graphql/field_type_in_block.rb @@ -7,15 +7,18 @@ module GraphQL # Identify (and auto-correct) any field whose type configuration isn't given # in the configuration block. # - # @example - # # bad, immediately causes Rails to load `app/graphql/types/thing.rb` - # field :thing, Types::Thing + # **Examples** # - # # good, defers loading until the file is needed - # field :thing do - # type(Types::Thing) - # end + # **Example: # bad, immediately causes Rails to load `app/graphql/types/thing.rb`** # + # ```ruby + # field :thing, Types::Thing + # + # # good, defers loading until the file is needed + # field :thing do + # type(Types::Thing) + # end + # ``` class FieldTypeInBlock < BaseCop MSG = "type configuration can be moved to a block to defer loading the type's file" diff --git a/lib/graphql/rubocop/graphql/root_types_in_block.rb b/lib/graphql/rubocop/graphql/root_types_in_block.rb index 80cbb0a5b21..2c3616cd91c 100644 --- a/lib/graphql/rubocop/graphql/root_types_in_block.rb +++ b/lib/graphql/rubocop/graphql/root_types_in_block.rb @@ -6,13 +6,16 @@ module Rubocop module GraphQL # Identify (and auto-correct) any root types in your schema file. # - # @example - # # bad, immediately causes Rails to load `app/graphql/types/query.rb` - # query Types::Query + # **Examples** # - # # good, defers loading until the file is needed - # query { Types::Query } + # **Example: # bad, immediately causes Rails to load `app/graphql/types/query.rb`** # + # ```ruby + # query Types::Query + # + # # good, defers loading until the file is needed + # query { Types::Query } + # ``` class RootTypesInBlock < BaseCop MSG = "type configuration can be moved to a block to defer loading the type's file" diff --git a/lib/graphql/schema.rb b/lib/graphql/schema.rb index f6801ddbfb2..4d9474f7de8 100644 --- a/lib/graphql/schema.rb +++ b/lib/graphql/schema.rb @@ -48,28 +48,125 @@ require "graphql/schema/visibility" module GraphQL - # A GraphQL schema which may be queried with {GraphQL::Query}. + # A GraphQL schema which may be queried with [GraphQL::Query](rdoc-ref:GraphQL::Query). # - # The {Schema} contains: + # The [Schema](rdoc-ref: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}. + # Schemas start with root types, [Schema.query](rdoc-ref:GraphQL::Schema::query), [Schema.mutation](rdoc-ref:GraphQL::Schema::mutation) and [Schema.subscription](rdoc-ref:GraphQL::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}) + # (These configurations can be overridden by specific calls to [Schema.execute](rdoc-ref:Schema.execute)) # - # @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 + # **Schema configuration reference** # + # - Root types are registered with `query`, `mutation`, and `subscription`; use `orphan_types` for interface-only object types. + # - `object_from_id`, `id_from_object`, and `resolve_type` implement Relay object identification and abstract-type resolution. + # - `type_error`, `rescue_from`, `parse_error`, and `query_stack_error` provide execution error hooks. + # - `max_depth`, `max_complexity`, `validate_timeout`, `validate_max_errors`, and `max_query_string_tokens` limit incoming work. + # - `extra_types`, `introspection`, `trace_with`, analyzers, `context_class`, `query_class`, `lazy_resolve`, and `use` configure execution. + # + # The [schema definition guide](/schema/definition) contains setup examples and + # links each contract above to its API method. Keep method-specific behavior in + # the comments for those methods so this page remains the source of truth. + # + # ## Root Types + # + # `query`, `mutation`, and `subscription` register the entry-point object types + # for a schema. Each may receive a type class or a block for lazy loading: + # + # ```ruby + # query Types::Query + # mutation { Types::Mutation } + # subscription { Types::Subscription } + # ``` + # + # Use [Schema.orphan_types](rdoc-ref:GraphQL::Schema.orphan_types) for object + # types which implement an interface but aren't reachable from a field return + # type. Use [Schema.extra_types](rdoc-ref:GraphQL::Schema.extra_types) when a + # type should be printed and included in introspection without being connected + # to the schema's type graph. + # + # ## Object Identification + # + # Relay-style `node(id:)` fields, arguments configured with `loads:`, and the + # ObjectCache use [Schema.object_from_id](rdoc-ref:GraphQL::Schema.object_from_id) + # to fetch an application object. Return `nil` when the object does not exist or + # is not visible to the current operation. Implement + # [Schema.id_from_object](rdoc-ref:GraphQL::Schema.id_from_object) to produce a + # stable ID which can be passed back to `object_from_id`. + # + # [Schema.resolve_type](rdoc-ref:GraphQL::Schema.resolve_type) maps an + # application object to its runtime GraphQL type when a field returns an + # interface or union. + # + # ## Error Handling + # + # Override [Schema.type_error](rdoc-ref:GraphQL::Schema.type_error) to handle + # mismatches between application values and the GraphQL type system. Register + # application exception handlers with [Schema.rescue_from](rdoc-ref:GraphQL::Schema.rescue_from). + # [Schema.parse_error](rdoc-ref:GraphQL::Schema.parse_error) handles invalid + # query strings, and [Schema.query_stack_error](rdoc-ref:GraphQL::Schema.query_stack_error) + # is called when execution encounters a `SystemStackError`. + # + # ## Default Limits + # + # [Schema.max_depth](rdoc-ref:GraphQL::Schema.max_depth) limits nested field + # selections and [Schema.max_complexity](rdoc-ref:GraphQL::Schema.max_complexity) + # limits the calculated cost of a query. [Schema.default_max_page_size](rdoc-ref:GraphQL::Schema.default_max_page_size) + # limits connection fields. [Schema.validate_timeout](rdoc-ref:GraphQL::Schema.validate_timeout), + # [Schema.validate_max_errors](rdoc-ref:GraphQL::Schema.validate_max_errors), and + # [Schema.max_query_string_tokens](rdoc-ref:GraphQL::Schema.max_query_string_tokens) + # bound validation and parsing work. These limits can be configured on a schema + # and, where documented, overridden for an individual execution. + # + # ## Introspection + # + # [Schema.extra_types](rdoc-ref:GraphQL::Schema.extra_types) adds otherwise + # unreachable types to printed SDL and introspection results. Pass a custom + # namespace to [Schema.introspection](rdoc-ref:GraphQL::Schema.introspection) to + # replace or extend the default introspection system. + # + # ## Authorization + # + # [Schema.unauthorized_object](rdoc-ref:GraphQL::Schema.unauthorized_object) + # and [Schema.unauthorized_field](rdoc-ref:GraphQL::Schema.unauthorized_field) + # run when an authorization hook returns `false`. Return a replacement value or + # raise [GraphQL::ExecutionError](rdoc-ref:GraphQL::ExecutionError) to add a + # client-facing error. + # + # ## Execution Configuration + # + # [Schema.trace_with](rdoc-ref:GraphQL::Schema.trace_with) installs tracing + # modules. [Schema.query_analyzer](rdoc-ref:GraphQL::Schema.query_analyzer) and + # [Schema.multiplex_analyzer](rdoc-ref:GraphQL::Schema.multiplex_analyzer) + # register analysis hooks. [Schema.default_logger](rdoc-ref:GraphQL::Schema.default_logger) + # configures runtime logging, while [Schema.context_class](rdoc-ref:GraphQL::Schema.context_class) + # and [Schema.query_class](rdoc-ref:GraphQL::Schema.query_class) select the + # classes used during execution. [Schema.lazy_resolve](rdoc-ref:GraphQL::Schema.lazy_resolve) + # registers promise-like values, and [Schema.use](rdoc-ref:GraphQL::Schema.use) + # installs schema plugins such as Dataloader and Visibility. + # + # **Examples** + # + # **Example: defining a schema** + # + # ```ruby + # 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 + # ``` + # + # The API-specific portions of `guides/schema/definition.md` were migrated + # here; the guide remains a standalone setup tutorial. + # migrated from guides/schema/definition.md class Schema extend GraphQL::Schema::Member::HasAstNode extend GraphQL::Schema::FindInheritedValue @@ -100,18 +197,36 @@ class InvalidDocumentError < Error; end; class << 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` + # + # **Parameters** + # + # - `introspection_result` (`Hash`) — A response from [GraphQL::Introspection::INTROSPECTION_QUERY](rdoc-ref:GraphQL::Introspection::INTROSPECTION_QUERY) + # + # **Returns** + # + # - `Class` — the schema described by `input` + # + # :call-seq: + # from_introspection(Hash introspection_result) -> Class[GraphQL::Schema] def 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)` - # @return [Class] the schema described by `document` + # + # **Parameters** + # + # - `definition_or_path` (`String`) — A schema definition string, or a path to a file containing the definition + # - `default_resolve` (`<#call(type, field, obj, args, ctx)>`) — A callable for handling field resolution + # - `parser` (`Object`) — An object for handling definition string parsing (must respond to `parse`) + # - `using` (`Hash`) — Plugins to attach to the created schema with `use(key, value)` + # + # **Returns** + # + # - `Class` — the schema described by `document` + # + # :call-seq: + # from_definition(String definition_or_path, #call(type, field, obj, args, ctx) default_resolve:, Object parser:, Hash using:, base_types:) -> Class def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {}, base_types: {}) # If the file ends in `.graphql` or `.graphqls`, treat it like a filepath if definition_or_path.end_with?(".graphql") || definition_or_path.end_with?(".graphqls") @@ -139,7 +254,12 @@ def deprecated_graphql_definition graphql_definition(silence_deprecation_warning: true) end - # @return [GraphQL::Subscriptions] + # **Returns** + # + # - `GraphQL::Subscriptions` + # + # :call-seq: + # subscriptions(inherited:) -> GraphQL::Subscriptions def subscriptions(inherited: true) defined?(@subscriptions) ? @subscriptions : (inherited ? find_inherited_value(:subscriptions, nil) : nil) end @@ -148,7 +268,12 @@ def subscriptions=(new_implementation) @subscriptions = new_implementation end - # @param new_mode [Symbol] If configured, this will be used when `context: { trace_mode: ... }` isn't set. + # **Parameters** + # + # - `new_mode` (`Symbol`) — If configured, this will be used when `context: { trace_mode: ... }` isn't set. + # + # :call-seq: + # default_trace_mode(Symbol new_mode) def default_trace_mode(new_mode = NOT_CONFIGURED) if !NOT_CONFIGURED.equal?(new_mode) @default_trace_mode = new_mode @@ -175,7 +300,12 @@ def trace_class(new_class = nil) trace_class_for(:default, build: true) end - # @return [Class] Return the trace class to use for this mode, looking one up on the superclass if this Schema doesn't have one defined. + # **Returns** + # + # - `Class` — Return the trace class to use for this mode, looking one up on the superclass if this Schema doesn't have one defined. + # + # :call-seq: + # trace_class_for(mode, build:) -> Class def trace_class_for(mode, build: false) if (trace_class = own_trace_modes[mode]) trace_class @@ -189,7 +319,7 @@ def trace_class_for(mode, build: false) end # Configure `trace_class` to be used whenever `context: { trace_mode: mode_name }` is requested. - # {default_trace_mode} is used when no `trace_mode: ...` is requested. + # `default_trace_mode` is used when no `trace_mode: ...` is requested. # # When a `trace_class` is added this way, it will _not_ receive other modules added with `trace_with(...)` # unless `trace_mode` is explicitly given. (This class will not receive any default trace modules.) @@ -197,9 +327,17 @@ def trace_class_for(mode, build: false) # Subclasses of the schema will use `trace_class` as a base class for this mode and those # subclass also will _not_ receive default tracing modules. # - # @param mode_name [Symbol] - # @param trace_class [Class] subclass of GraphQL::Tracing::Trace - # @return void + # **Parameters** + # + # - `mode_name` (`Symbol`) + # - `trace_class` (`Class`) — subclass of GraphQL::Tracing::Trace + # + # **Returns** + # + # - `Object` — void + # + # :call-seq: + # trace_mode(Symbol mode_name, Class trace_class) -> Object def trace_mode(mode_name, trace_class) own_trace_modes[mode_name] = trace_class nil @@ -241,7 +379,12 @@ def own_trace_modules @own_trace_modules ||= Hash.new { |h, k| h[k] = [] } end - # @return [Array] Modules added for tracing in `trace_mode`, including inherited ones + # **Returns** + # + # - `Array` — Modules added for tracing in `trace_mode`, including inherited ones + # + # :call-seq: + # trace_modules_for(trace_mode) -> Array[Module] def trace_modules_for(trace_mode) modules = own_trace_modules[trace_mode] if superclass.respond_to?(:trace_modules_for) @@ -251,21 +394,36 @@ def trace_modules_for(trace_mode) end - # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}. - # @see #as_json Return a Hash representation of the schema - # @return [String] + # Returns the JSON response of [Introspection::INTROSPECTION_QUERY](rdoc-ref:Introspection::INTROSPECTION_QUERY). + # See [as_json](rdoc-ref:GraphQL::Schema::as_json) Return a Hash representation of the schema + # + # **Returns** + # + # - `String` + # + # :call-seq: + # to_json(**args) -> String def to_json(**args) JSON.pretty_generate(as_json(**args)) end - # Return the Hash response of {Introspection::INTROSPECTION_QUERY}. - # @param context [Hash] - # @param include_deprecated_args [Boolean] If true, deprecated arguments will be included in the JSON response - # @param include_schema_description [Boolean] If true, the schema's description will be queried and included in the response - # @param include_is_repeatable [Boolean] If true, `isRepeatable: true|false` will be included with the schema's directives - # @param include_specified_by_url [Boolean] If true, scalar types' `specifiedByUrl:` will be included in the response - # @param include_is_one_of [Boolean] If true, `isOneOf: true|false` will be included with input objects - # @return [Hash] GraphQL result + # Return the Hash response of [Introspection::INTROSPECTION_QUERY](rdoc-ref:Introspection::INTROSPECTION_QUERY). + # + # **Parameters** + # + # - `context` (`Hash`) + # - `include_deprecated_args` (`Boolean`) — If true, deprecated arguments will be included in the JSON response + # - `include_schema_description` (`Boolean`) — If true, the schema's description will be queried and included in the response + # - `include_is_repeatable` (`Boolean`) — If true, `isRepeatable: true|false` will be included with the schema's directives + # - `include_specified_by_url` (`Boolean`) — If true, scalar types' `specifiedByUrl:` will be included in the response + # - `include_is_one_of` (`Boolean`) — If true, `isOneOf: true|false` will be included with input objects + # + # **Returns** + # + # - `Hash` — GraphQL result + # + # :call-seq: + # as_json(Hash context:, bool include_deprecated_args:, bool include_schema_description:, bool include_is_repeatable:, bool include_specified_by_url:, bool include_is_one_of:) -> Hash def as_json(context: {}, include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false) introspection_query = Introspection.query( include_deprecated_args: include_deprecated_args, @@ -279,19 +437,39 @@ def as_json(context: {}, include_deprecated_args: true, include_schema_descripti end # Return the GraphQL IDL for the schema - # @param context [Hash] - # @return [String] + # + # **Parameters** + # + # - `context` (`Hash`) + # + # **Returns** + # + # - `String` + # + # :call-seq: + # to_definition(Hash context:) -> String def to_definition(context: {}) GraphQL::Schema::Printer.print_schema(self, context: context) end # Return the GraphQL::Language::Document IDL AST for the schema - # @return [GraphQL::Language::Document] + # + # **Returns** + # + # - `GraphQL::Language::Document` + # + # :call-seq: + # to_document() -> GraphQL::Language::Document def to_document GraphQL::Language::DocumentFromSchemaDefinition.new(self).document end - # @return [String, nil] + # **Returns** + # + # - `String, nil` + # + # :call-seq: + # description(new_description) -> String | nil def description(new_description = nil) if new_description @description = new_description @@ -315,8 +493,17 @@ def static_validator end # Add `plugin` to this schema - # @param plugin [#use] A Schema plugin - # @return void + # + # **Parameters** + # + # - `plugin` (`#use`) — A Schema plugin + # + # **Returns** + # + # - `Object` — void + # + # :call-seq: + # use(#use plugin, **kwargs) -> Object def use(plugin, **kwargs) if !kwargs.empty? plugin.use(self, **kwargs) @@ -336,9 +523,15 @@ def null_context @null_context || GraphQL::Query::NullContext.instance 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. + # Build a map of `{ name => type }` and return it. + # `get_type` is more efficient for finding _one type_ by name, because it doesn't merge hashes. + # + # **Returns** + # + # - `Hash Class>` — A dictionary of type classes by their GraphQL name + # + # :call-seq: + # types(context) -> Hash[String, Class] def types(context = null_context) if use_visibility_profile? types = Visibility::Profile.from_context(context, self) @@ -368,10 +561,18 @@ def types(context = null_context) visible_types end - # @param type_name [String] - # @param context [GraphQL::Query::Context] Used for filtering definitions at query-time - # @param use_visibility_profile Private, for migration to {Schema::Visibility} - # @return [Module, nil] A type, or nil if there's no type called `type_name` + # **Parameters** + # + # - `type_name` (`String`) + # - `context` (`GraphQL::Query::Context`) — Used for filtering definitions at query-time + # - `use_visibility_profile` — Private, for migration to [Schema::Visibility](rdoc-ref:Schema::Visibility) + # + # **Returns** + # + # - `Module, nil` — A type, or nil if there's no type called `type_name` + # + # :call-seq: + # get_type(String type_name, GraphQL::Query::Context context, use_visibility_profile) -> Module | nil def get_type(type_name, context = null_context, use_visibility_profile = use_visibility_profile?) if use_visibility_profile profile = Visibility::Profile.from_context(context, self) @@ -411,15 +612,24 @@ def get_type(type_name, context = null_context, use_visibility_profile = use_vis (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context, use_visibility_profile) : nil) end - # @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility? + # **Returns** + # + # - `Boolean` — Does this schema have _any_ definition for a type named `type_name`, regardless of visibility? + # + # :call-seq: + # has_defined_type?(type_name) -> bool def has_defined_type?(type_name) own_types.key?(type_name) || introspection_system.types.key?(type_name) || (superclass.respond_to?(:has_defined_type?) ? superclass.has_defined_type?(type_name) : false) end - # @api private - attr_writer :connections + attr_writer :connections # :nodoc: - # @return [GraphQL::Pagination::Connections] if installed + # **Returns** + # + # - `GraphQL::Pagination::Connections` — if installed + # + # :call-seq: + # connections() -> GraphQL::Pagination::Connections def connections if defined?(@connections) @connections @@ -435,14 +645,27 @@ def connections end end - # Get or set the root `query { ... }` object for this schema. + # Get or set the root `query` object for this schema. + # + # **Examples** + # + # **Example: Using `Types::Query` as the entry-point** # - # @example Using `Types::Query` as the entry-point - # query { Types::Query } + # ```ruby + # query { Types::Query } + # ``` # - # @param new_query_object [Class] The root type to use for queries - # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root query type. - # @return [Class, nil] The configured query root type, if there is one. + # **Parameters** + # + # - `new_query_object` (`Class`) — The root type to use for queries + # - `lazy_load_block` — If a block is given, then it will be called when GraphQL-Ruby needs the root query type. + # + # **Returns** + # + # - `Class, nil` — The configured query root type, if there is one. + # + # :call-seq: + # query(Class[GraphQL::Schema::Object] new_query_object, &lazy_load_block) -> Class[GraphQL::Schema::Object] | nil def query(new_query_object = nil, &lazy_load_block) if new_query_object || block_given? if @query_object @@ -474,14 +697,27 @@ def query(new_query_object = nil, &lazy_load_block) end end - # Get or set the root `mutation { ... }` object for this schema. + # Get or set the root `mutation` object for this schema. + # + # **Examples** # - # @example Using `Types::Mutation` as the entry-point - # mutation { Types::Mutation } + # **Example: Using `Types::Mutation` as the entry-point** # - # @param new_mutation_object [Class] The root type to use for mutations - # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root mutation type. - # @return [Class, nil] The configured mutation root type, if there is one. + # ```ruby + # mutation { Types::Mutation } + # ``` + # + # **Parameters** + # + # - `new_mutation_object` (`Class`) — The root type to use for mutations + # - `lazy_load_block` — If a block is given, then it will be called when GraphQL-Ruby needs the root mutation type. + # + # **Returns** + # + # - `Class, nil` — The configured mutation root type, if there is one. + # + # :call-seq: + # mutation(Class[GraphQL::Schema::Object] new_mutation_object, &lazy_load_block) -> Class[GraphQL::Schema::Object] | nil def mutation(new_mutation_object = nil, &lazy_load_block) if new_mutation_object || block_given? if @mutation_object @@ -513,14 +749,27 @@ def mutation(new_mutation_object = nil, &lazy_load_block) end end - # Get or set the root `subscription { ... }` object for this schema. + # Get or set the root `subscription` object for this schema. + # + # **Examples** + # + # **Example: Using `Types::Subscription` as the entry-point** + # + # ```ruby + # subscription { Types::Subscription } + # ``` + # + # **Parameters** # - # @example Using `Types::Subscription` as the entry-point - # subscription { Types::Subscription } + # - `new_subscription_object` (`Class`) — The root type to use for subscriptions + # - `lazy_load_block` — If a block is given, then it will be called when GraphQL-Ruby needs the root subscription type. # - # @param new_subscription_object [Class] The root type to use for subscriptions - # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root subscription type. - # @return [Class, nil] The configured subscription root type, if there is one. + # **Returns** + # + # - `Class, nil` — The configured subscription root type, if there is one. + # + # :call-seq: + # subscription(Class[GraphQL::Schema::Object] new_subscription_object, &lazy_load_block) -> Class[GraphQL::Schema::Object] | nil def subscription(new_subscription_object = nil, &lazy_load_block) if new_subscription_object || block_given? if @subscription_object @@ -555,8 +804,7 @@ def subscription(new_subscription_object = nil, &lazy_load_block) end end - # @api private - def root_type_for_operation(operation) + def root_type_for_operation(operation) # :nodoc: case operation when "query" query @@ -569,7 +817,12 @@ def root_type_for_operation(operation) end end - # @return [Array] The root types (query, mutation, subscription) defined for this schema + # **Returns** + # + # - `Array` — The root types (query, mutation, subscription) defined for this schema + # + # :call-seq: + # root_types() -> Array[Class] def root_types if use_visibility_profile? [query, mutation, subscription].compact @@ -578,8 +831,7 @@ def root_types end end - # @api private - def warden_class + def warden_class # :nodoc: if defined?(@warden_class) @warden_class elsif superclass.respond_to?(:warden_class) @@ -589,11 +841,9 @@ def warden_class end end - # @api private - attr_writer :warden_class + attr_writer :warden_class # :nodoc: - # @api private - def visibility_profile_class + def visibility_profile_class # :nodoc: if defined?(@visibility_profile_class) @visibility_profile_class elsif superclass.respond_to?(:visibility_profile_class) @@ -603,12 +853,9 @@ def visibility_profile_class end end - # @api private - attr_writer :visibility_profile_class, :use_visibility_profile - # @api private - attr_accessor :visibility - # @api private - def use_visibility_profile? + attr_writer :visibility_profile_class, :use_visibility_profile # :nodoc: + attr_accessor :visibility # :nodoc: + def use_visibility_profile? # :nodoc: if defined?(@use_visibility_profile) @use_visibility_profile elsif superclass.respond_to?(:use_visibility_profile?) @@ -618,11 +865,19 @@ def use_visibility_profile? end end - # @param type [Module] The type definition whose possible types you want to see - # @param context [GraphQL::Query::Context] used for filtering visible possible types at runtime - # @param use_visibility_profile Private, for migration to {Schema::Visibility} - # @return [Hash] All possible types, if no `type` is given. - # @return [Array] Possible types for `type`, if it's given. + # **Parameters** + # + # - `type` (`Module`) — The type definition whose possible types you want to see + # - `context` (`GraphQL::Query::Context`) — used for filtering visible possible types at runtime + # - `use_visibility_profile` — Private, for migration to [Schema::Visibility](rdoc-ref:Schema::Visibility) + # + # **Returns** + # + # - `Hash` — All possible types, if no `type` is given. + # - `Array` — Possible types for `type`, if it's given. + # + # :call-seq: + # possible_types(Module type, GraphQL::Query::Context context, use_visibility_profile) -> Hash[String, Module] | Array[Module] def possible_types(type = nil, context = null_context, use_visibility_profile = use_visibility_profile?) if use_visibility_profile if type @@ -674,9 +929,8 @@ def union_memberships(type = nil) end end - # @api private - # @see GraphQL::Dataloader - def dataloader_class + # See [GraphQL::Dataloader](rdoc-ref:GraphQL::Dataloader) GraphQL::Dataloader + def dataloader_class # :nodoc: @dataloader_class || GraphQL::Dataloader::NullDataloader end @@ -749,8 +1003,17 @@ def get_fields(type, context = null_context) end # Pass a custom introspection module here to use it for this schema. - # @param new_introspection_namespace [Module] If given, use this module for custom introspection on the schema - # @return [Module, nil] The configured namespace, if there is one + # + # **Parameters** + # + # - `new_introspection_namespace` (`Module`) — If given, use this module for custom introspection on the schema + # + # **Returns** + # + # - `Module, nil` — The configured namespace, if there is one + # + # :call-seq: + # introspection(Module new_introspection_namespace) -> Module | nil def introspection(new_introspection_namespace = nil) if new_introspection_namespace @introspection = new_introspection_namespace @@ -764,7 +1027,12 @@ def introspection(new_introspection_namespace = nil) end end - # @return [Schema::IntrospectionSystem] Based on {introspection} + # **Returns** + # + # - `Schema::IntrospectionSystem` — Based on [introspection](rdoc-ref:introspection) + # + # :call-seq: + # introspection_system() -> Schema::IntrospectionSystem def introspection_system if !@introspection_system @introspection_system = Schema::IntrospectionSystem.new(self) @@ -790,7 +1058,13 @@ def default_max_page_size(new_default_max_page_size = nil) # A limit on the number of tokens to accept on incoming query strings. # Use this to prevent parsing maliciously-large query strings. - # @return [nil, Integer] + # + # **Returns** + # + # - `nil, Integer` + # + # :call-seq: + # max_query_string_tokens(new_max_tokens) -> nil | Integer def max_query_string_tokens(new_max_tokens = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_max_tokens) defined?(@max_query_string_tokens) ? @max_query_string_tokens : find_inherited_value(:max_query_string_tokens) @@ -856,8 +1130,17 @@ def validate_timeout(new_validate_timeout = NOT_CONFIGURED) end # Validate a query string according to this schema. - # @param string_or_document [String, GraphQL::Language::Nodes::Document] - # @return [Array] + # + # **Parameters** + # + # - `string_or_document` (`String, GraphQL::Language::Nodes::Document`) + # + # **Returns** + # + # - `Array` + # + # :call-seq: + # validate(String | GraphQL::Language::Nodes::Document string_or_document, rules:, context:) -> Array[GraphQL::StaticValidation::Error] def validate(string_or_document, rules: nil, context: nil) doc = if string_or_document.is_a?(String) GraphQL.parse(string_or_document, max_tokens: max_query_string_tokens) @@ -872,7 +1155,12 @@ def validate(string_or_document, rules: nil, context: nil) res[:errors] end - # @param new_query_class [Class] A subclass to use when executing queries + # **Parameters** + # + # - `new_query_class` (`Class`) — A subclass to use when executing queries + # + # :call-seq: + # query_class(Class[GraphQL::Query] new_query_class) def query_class(new_query_class = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_query_class) @query_class || (superclass.respond_to?(:query_class) ? superclass.query_class : GraphQL::Query) @@ -992,8 +1280,16 @@ def disable_type_introspection_entry_point? end end - # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema - # @return [Array] Type definitions added to this schema + # **Parameters** + # + # - `new_extra_types` (`Module`) — Type definitions to include in printing and introspection, even though they aren't referenced in the schema + # + # **Returns** + # + # - `Array` — Type definitions added to this schema + # + # :call-seq: + # extra_types(Module *new_extra_types) -> Array[Module] def extra_types(*new_extra_types) if !new_extra_types.empty? new_extra_types = new_extra_types.flatten @@ -1017,8 +1313,16 @@ def extra_types(*new_extra_types) # This method must be used when an object type is connected to the schema as an interface implementor but # not as a return type of a field. In that case, if the object type isn't registered here, GraphQL-Ruby won't be able to find it. # - # @param new_orphan_types [Array>] Object types to register as implementations of interfaces in the schema. - # @return [Array>] All previously-registered orphan types for this schema + # **Parameters** + # + # - `new_orphan_types` (`Array>`) — Object types to register as implementations of interfaces in the schema. + # + # **Returns** + # + # - `Array>` — All previously-registered orphan types for this schema + # + # :call-seq: + # orphan_types(Array[Class[GraphQL::Schema::Object]] *new_orphan_types) -> Array[Class[GraphQL::Schema::Object]] def orphan_types(*new_orphan_types) if !new_orphan_types.empty? new_orphan_types = new_orphan_types.flatten @@ -1067,7 +1371,12 @@ def default_analysis_engine end - # @param new_default_logger [#log] Something to use for logging messages + # **Parameters** + # + # - `new_default_logger` (`#log`) — Something to use for logging messages + # + # :call-seq: + # default_logger(#log new_default_logger) def default_logger(new_default_logger = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_default_logger) if defined?(@default_logger) @@ -1088,8 +1397,16 @@ def default_logger(new_default_logger = NOT_CONFIGURED) end end - # @param context [GraphQL::Query::Context, nil] - # @return [Logger] A logger to use for this context configuration, falling back to {.default_logger} + # **Parameters** + # + # - `context` (`GraphQL::Query::Context, nil`) + # + # **Returns** + # + # - `Logger` — A logger to use for this context configuration, falling back to [.default_logger](rdoc-ref:.default_logger) + # + # :call-seq: + # logger_for(GraphQL::Query::Context | nil context) -> Logger def logger_for(context) if context && context[:logger] == false Logger.new(IO::NULL) @@ -1100,7 +1417,12 @@ def logger_for(context) end end - # @param new_context_class [Class] A subclass to use when executing queries + # **Parameters** + # + # - `new_context_class` (`Class`) — A subclass to use when executing queries + # + # :call-seq: + # context_class(Class[GraphQL::Query::Context] new_context_class) def context_class(new_context_class = nil) if new_context_class @context_class = new_context_class @@ -1111,18 +1433,34 @@ def context_class(new_context_class = nil) # Register a handler for errors raised during execution. The handlers can return a new value or raise a new error. # - # @example Handling "not found" with a client-facing error - # rescue_from(ActiveRecord::NotFound) { raise GraphQL::ExecutionError, "An object could not be found" } - # - # @param err_classes [Array] Classes which should be rescued by `handler_block` - # @param handler_block The code to run when one of those errors is raised during execution - # @yieldparam error [StandardError] An instance of one of the configured `err_classes` - # @yieldparam object [Object] The current application object in the query when the error was raised - # @yieldparam arguments [GraphQL::Query::Arguments] The current field arguments when the error was raised - # @yieldparam context [GraphQL::Query::Context] The context for the currently-running operation - # @yieldreturn [Object] Some object to use in the place where this error was raised - # @raise [GraphQL::ExecutionError] In the handler, raise to add a client-facing error to the response - # @raise [StandardError] In the handler, raise to crash the query with a developer-facing error + # **Examples** + # + # **Example: Handling "not found" with a client-facing error** + # + # ```ruby + # rescue_from(ActiveRecord::NotFound) { raise GraphQL::ExecutionError, "An object could not be found" } + # ``` + # + # **Parameters** + # + # - `err_classes` (`Array`) — Classes which should be rescued by `handler_block` + # - `handler_block` — The code to run when one of those errors is raised during execution + # + # **Yields** + # + # - `error` (`StandardError`) — An instance of one of the configured `err_classes` + # - `object` (`Object`) — The current application object in the query when the error was raised + # - `arguments` (`GraphQL::Query::Arguments`) — The current field arguments when the error was raised + # - `context` (`GraphQL::Query::Context`) — The context for the currently-running operation + # - `Object` — Some object to use in the place where this error was raised + # + # **Raises** + # + # - `GraphQL::ExecutionError` — In the handler, raise to add a client-facing error to the response + # - `StandardError` — In the handler, raise to crash the query with a developer-facing error + # + # :call-seq: + # rescue_from(Array[StandardError] *err_classes, &handler_block) def rescue_from(*err_classes, &handler_block) err_classes.each do |err_class| Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block) @@ -1146,11 +1484,9 @@ def error_handlers end end - # @api private - attr_accessor :using_backtrace + attr_accessor :using_backtrace # :nodoc: - # @api private - def handle_or_reraise(context, err, object: context[:current_object], arguments: context[:current_arguments], field: context[:current_field]) + def handle_or_reraise(context, err, object: context[:current_object], arguments: context[:current_arguments], field: context[:current_field]) # :nodoc: handler = Execution::Errors.find_handler_for(self, err.class) if handler arguments = arguments.respond_to?(:keyword_arguments) ? arguments.keyword_arguments : arguments @@ -1196,20 +1532,34 @@ def resolve_type(type, obj, ctx) # GraphQL-Ruby calls this method during execution when it needs the application to determine the type to use for an object. # - # Usually, this object was returned from a field whose return type is an {GraphQL::Schema::Interface} or a {GraphQL::Schema::Union}. - # But this method is called in other cases, too -- for example, when {GraphQL::Schema::Argument#loads} cases an object to be directly loaded from the database. + # Usually, this object was returned from a field whose return type is an [GraphQL::Schema::Interface](rdoc-ref:GraphQL::Schema::Interface) or a [GraphQL::Schema::Union](rdoc-ref:GraphQL::Schema::Union). + # But this method is called in other cases, too -- for example, when [GraphQL::Schema::Argument#loads](rdoc-ref:GraphQL::Schema::Argument#loads) cases an object to be directly loaded from the database. + # + # **Examples** # - # @example Returning a GraphQL type based on the object's class name - # class MySchema < GraphQL::Schema - # def resolve_type(_abs_type, object, _context) - # graphql_type_name = "Types::#{object.class.name}Type" - # graphql_type_name.constantize # If this raises a NameError, then come implement special cases in this method - # end + # **Example: Returning a GraphQL type based on the object's class name** + # + # ```ruby + # class MySchema < GraphQL::Schema + # def resolve_type(_abs_type, object, _context) + # graphql_type_name = "Types::#{object.class.name}Type" + # graphql_type_name.constantize # If this raises a NameError, then come implement special cases in this method # end - # @param abstract_type [Class, Module, nil] The Interface or Union type which is being resolved, if there is one - # @param application_object [Object] The object returned from a field whose type must be determined - # @param context [GraphQL::Query::Context] The query context for the currently-executing query - # @return [Class` — The Object type definition to use for `obj` + # + # :call-seq: + # resolve_type(Class | Module | nil abstract_type, Object application_object, GraphQL::Query::Context context) -> Class[GraphQL::Schema::Object] def resolve_type(abstract_type, application_object, context) raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(abstract_type, application_object, context) must be implemented to use Union types, Interface types, `loads:`, or `run_partials` (tried to resolve: #{abstract_type.name})" end @@ -1238,32 +1588,60 @@ def inherited(child_class) # Fetch an object based on an incoming ID and the current context. This method should return an object # from your application, or return `nil` if there is no object or the object shouldn't be available to this operation. # - # @example Fetching an object with Rails's GlobalID - # def self.object_from_id(object_id, _context) - # GlobalID.find(global_id) - # # TODO: use `context[:current_user]` to determine if this object is authorized. - # end - # @param object_id [String] The ID to fetch an object for. This may be client-provided (as in `node(id: ...)` or `loads:`) or previously stored by the schema (eg, by the `ObjectCache`) - # @param context [GraphQL::Query::Context] The context for the currently-executing operation - # @return [Object, nil] The application which `object_id` references, or `nil` if there is no object or the current operation shouldn't have access to the object - # @see id_from_object which produces these IDs + # See [id_from_object](rdoc-ref:id_from_object) which produces these IDs + # + # **Examples** + # + # **Example: Fetching an object with Rails's GlobalID** + # + # ```ruby + # def self.object_from_id(object_id, _context) + # GlobalID.find(global_id) + # # TODO: use `context[:current_user]` to determine if this object is authorized. + # end + # ``` + # + # **Parameters** + # + # - `object_id` (`String`) — The ID to fetch an object for. This may be client-provided (as in `node(id: ...)` or `loads:`) or previously stored by the schema (eg, by the `ObjectCache`) + # - `context` (`GraphQL::Query::Context`) — The context for the currently-executing operation + # + # **Returns** + # + # - `Object, nil` — The application which `object_id` references, or `nil` if there is no object or the current operation shouldn't have access to the object + # + # :call-seq: + # object_from_id(String object_id, GraphQL::Query::Context context) -> Object | nil def object_from_id(object_id, context) raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(object_id, context) must be implemented to load by ID (tried to load from id `#{object_id}`)" end - # Return a stable ID string for `object` so that it can be refetched later, using {.object_from_id}. + # Return a stable ID string for `object` so that it can be refetched later, using [.object_from_id](rdoc-ref:.object_from_id). # # [GlobalID](https://github.com/rails/globalid) and [SQIDs](https://sqids.org/ruby) can both be used to create IDs. # - # @example Using Rails's GlobalID to generate IDs - # def self.id_from_object(application_object, graphql_type, context) - # application_object.to_gid_param - # end + # **Examples** + # + # **Example: Using Rails's GlobalID to generate IDs** + # + # ```ruby + # def self.id_from_object(application_object, graphql_type, context) + # application_object.to_gid_param + # end + # ``` + # + # **Parameters** # - # @param application_object [Object] Some object encountered by GraphQL-Ruby while running a query - # @param graphql_type [Class, Module] The type that GraphQL-Ruby is using for `application_object` during this query - # @param context [GraphQL::Query::Context] The context for the operation that is currently running - # @return [String] A stable identifier which can be passed to {.object_from_id} later to re-fetch `application_object` + # - `application_object` (`Object`) — Some object encountered by GraphQL-Ruby while running a query + # - `graphql_type` (`Class, Module`) — The type that GraphQL-Ruby is using for `application_object` during this query + # - `context` (`GraphQL::Query::Context`) — The context for the operation that is currently running + # + # **Returns** + # + # - `String` — A stable identifier which can be passed to [.object_from_id](rdoc-ref:.object_from_id) later to re-fetch `application_object` + # + # :call-seq: + # id_from_object(Object application_object, Class | Module graphql_type, GraphQL::Query::Context context) -> String def id_from_object(application_object, graphql_type, context) raise GraphQL::RequiredImplementationMissingError, "#{self.name}.id_from_object(application_object, graphql_type, context) must be implemented to create global ids (tried to create an id for `#{application_object.inspect}`)" end @@ -1295,11 +1673,19 @@ def load_type(type_name, ctx) # 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} + # If you want to add an error to the `"errors"` key, raise a [GraphQL::ExecutionError](rdoc-ref:GraphQL::ExecutionError) # in this hook. # - # @param unauthorized_error [GraphQL::UnauthorizedError] - # @return [Object] The returned object will be put in the GraphQL response + # **Parameters** + # + # - `unauthorized_error` (`GraphQL::UnauthorizedError`) + # + # **Returns** + # + # - `Object` — The returned object will be put in the GraphQL response + # + # :call-seq: + # unauthorized_object(GraphQL::UnauthorizedError unauthorized_error) -> Object def unauthorized_object(unauthorized_error) nil end @@ -1311,11 +1697,19 @@ def unauthorized_object(unauthorized_error) # 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} + # If you want to add an error to the `"errors"` key, raise a [GraphQL::ExecutionError](rdoc-ref:GraphQL::ExecutionError) # in this hook. # - # @param unauthorized_error [GraphQL::UnauthorizedFieldError] - # @return [Field] The returned field will be put in the GraphQL response + # **Parameters** + # + # - `unauthorized_error` (`GraphQL::UnauthorizedFieldError`) + # + # **Returns** + # + # - `Field` — The returned field will be put in the GraphQL response + # + # :call-seq: + # unauthorized_field(GraphQL::UnauthorizedFieldError unauthorized_error) -> Field def unauthorized_field(unauthorized_error) unauthorized_object(unauthorized_error) end @@ -1325,11 +1719,23 @@ def unauthorized_field(unauthorized_error) # # The default implementation of this method is to follow the GraphQL specification, # but you can override this to report errors to your bug tracker or customize error handling. - # @param type_error [GraphQL::Error] several specific error classes are passed here, see the default implementation for details - # @param context [GraphQL::Query::Context] the context for the currently-running operation - # @return [void] - # @raise [GraphQL::ExecutionError] to return this error to the client - # @raise [GraphQL::Error] to crash the query and raise a developer-facing error + # + # **Parameters** + # + # - `type_error` (`GraphQL::Error`) — several specific error classes are passed here, see the default implementation for details + # - `context` (`GraphQL::Query::Context`) — the context for the currently-running operation + # + # **Returns** + # + # - `void` + # + # **Raises** + # + # - `GraphQL::ExecutionError` — to return this error to the client + # - `GraphQL::Error` — to crash the query and raise a developer-facing error + # + # :call-seq: + # type_error(GraphQL::Error type_error, GraphQL::Query::Context context) -> void | GraphQL::ExecutionError | GraphQL::Error def type_error(type_error, context) case type_error when GraphQL::InvalidNullError @@ -1345,12 +1751,21 @@ def type_error(type_error, context) end end - # A function to call when {.execute} receives an invalid query string + # A function to call when [.execute](rdoc-ref:.execute) receives an invalid query string # # The default is to add the error to `context.errors` - # @param parse_err [GraphQL::ParseError] The error encountered during parsing - # @param ctx [GraphQL::Query::Context] The context for the query where the error occurred - # @return void + # + # **Parameters** + # + # - `parse_err` (`GraphQL::ParseError`) — The error encountered during parsing + # - `ctx` (`GraphQL::Query::Context`) — The context for the query where the error occurred + # + # **Returns** + # + # - `Object` — void + # + # :call-seq: + # parse_error(GraphQL::ParseError parse_err, GraphQL::Query::Context ctx) -> Object def parse_error(parse_err, ctx) ctx.errors.push(parse_err) end @@ -1380,7 +1795,13 @@ def instrument(instrument_step, instrumenter, options = {}) end # Add several directives at once - # @param new_directives [Class] + # + # **Parameters** + # + # - `new_directives` (`Class`) + # + # :call-seq: + # directives(Class *new_directives) def directives(*new_directives) if !new_directives.empty? new_directives.flatten.each { |d| directive(d) } @@ -1395,8 +1816,17 @@ def directives(*new_directives) end # Attach a single directive to this schema - # @param new_directive [Class] - # @return void + # + # **Parameters** + # + # - `new_directive` (`Class`) + # + # **Returns** + # + # - `Object` — void + # + # :call-seq: + # directive(Class new_directive) -> Object def directive(new_directive) if use_visibility_profile? own_directives[new_directive.graphql_name] = new_directive @@ -1415,12 +1845,26 @@ def default_directives }.freeze end - # @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema + # **Returns** + # + # - `GraphQL::Tracing::DetailedTrace` — if it has been configured for this schema + # + # :call-seq: + # detailed_trace -> GraphQL::Tracing::DetailedTrace attr_accessor :detailed_trace - # @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex}) - # @return [Boolean] When `true`, save a detailed trace for this query. - # @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true + # See [Tracing::DetailedTrace](rdoc-ref:Tracing::DetailedTrace) DetailedTrace saves traces when this method returns true + # + # **Parameters** + # + # - `query` (`GraphQL::Query, GraphQL::Execution::Multiplex`) — Called with a multiplex when multiple queries are executed at once (with [.multiplex](rdoc-ref:.multiplex)) + # + # **Returns** + # + # - `Boolean` — When `true`, save a detailed trace for this query. + # + # :call-seq: + # detailed_trace?(GraphQL::Query | GraphQL::Execution::Multiplex query) -> bool def detailed_trace?(query) raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition." end @@ -1458,15 +1902,29 @@ def tracers # # Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration). # - # @example Adding a trace in a special mode - # # only runs when `query.context[:trace_mode]` is `:special` - # trace_with SpecialTrace, mode: :special + # See [GraphQL::Tracing::Trace](rdoc-ref:GraphQL::Tracing::Trace) Tracing::Trace for available tracing methods + # + # **Examples** + # + # **Example: Adding a trace in a special mode** + # + # ```ruby + # # only runs when `query.context[:trace_mode]` is `:special` + # trace_with SpecialTrace, mode: :special + # ``` + # + # **Parameters** # - # @param trace_mod [Module] A module that implements tracing methods - # @param mode [Symbol] Trace module will only be used for this trade mode - # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize` - # @return [void] - # @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods + # - `trace_mod` (`Module`) — A module that implements tracing methods + # - `mode` (`Symbol`) — Trace module will only be used for this trade mode + # - `options` (`Hash`) — Keywords that will be passed to the tracing class during `#initialize` + # + # **Returns** + # + # - `void` + # + # :call-seq: + # trace_with(Module trace_mod, Symbol mode:, Hash **options) -> void def trace_with(trace_mod, mode: :default, **options) if mode.is_a?(Array) mode.each { |m| trace_with(trace_mod, mode: m, **options) } @@ -1496,7 +1954,13 @@ def trace_with(trace_mod, mode: :default, **options) end # The options hash for this trace mode - # @return [Hash] + # + # **Returns** + # + # - `Hash` + # + # :call-seq: + # trace_options_for(mode) -> Hash def trace_options_for(mode) @trace_options_for_mode ||= {} @trace_options_for_mode[mode] ||= begin @@ -1514,14 +1978,22 @@ def trace_options_for(mode) # Create a trace instance which will include the trace modules specified for the optional mode. # - # If no `mode:` is given, then {default_trace_mode} will be used. + # If no `mode:` is given, then [default_trace_mode](rdoc-ref:default_trace_mode) will be used. # - # If this schema is using {Tracing::DetailedTrace} and {.detailed_trace?} returns `true`, then + # If this schema is using [Tracing::DetailedTrace](rdoc-ref:Tracing::DetailedTrace) and [.detailed_trace?](rdoc-ref:.detailed_trace?) returns `true`, then # DetailedTrace's mode will override the passed-in `mode`. # - # @param mode [Symbol] Trace modules for this trade mode will be included - # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize` - # @return [Tracing::Trace] + # **Parameters** + # + # - `mode` (`Symbol`) — Trace modules for this trade mode will be included + # - `options` (`Hash`) — Keywords that will be passed to the tracing class during `#initialize` + # + # **Returns** + # + # - `Tracing::Trace` + # + # :call-seq: + # new_trace(Symbol mode:, Hash **options) -> Tracing::Trace def new_trace(mode: nil, **options) should_sample = if detailed_trace if (query = options[:query]) @@ -1551,8 +2023,14 @@ def new_trace(mode: nil, **options) trace_class_for_mode.new(**trace_options) end - # @param new_analyzer [Class] An analyzer to run on queries to this schema - # @see GraphQL::Analysis the analysis system + # See [GraphQL::Analysis](rdoc-ref:GraphQL::Analysis) the analysis system + # + # **Parameters** + # + # - `new_analyzer` (`Class`) — An analyzer to run on queries to this schema + # + # :call-seq: + # query_analyzer(Class[GraphQL::Analysis::Analyzer] new_analyzer) def query_analyzer(new_analyzer) own_query_analyzers << new_analyzer end @@ -1562,8 +2040,14 @@ def query_analyzers inherited_qa.empty? ? own_query_analyzers : (inherited_qa + own_query_analyzers) end - # @param new_analyzer [Class] An analyzer to run on multiplexes to this schema - # @see GraphQL::Analysis the analysis system + # See [GraphQL::Analysis](rdoc-ref:GraphQL::Analysis) the analysis system + # + # **Parameters** + # + # - `new_analyzer` (`Class`) — An analyzer to run on multiplexes to this schema + # + # :call-seq: + # multiplex_analyzer(Class[GraphQL::Analysis::Analyzer] new_analyzer) def multiplex_analyzer(new_analyzer) own_multiplex_analyzers << new_analyzer end @@ -1581,8 +2065,19 @@ def sanitized_printer(new_sanitized_printer = nil) end # Execute a query on itself. - # @see {Query#initialize} for arguments. - # @return [GraphQL::Query::Result] query result, ready to be serialized as JSON + # See the [GraphQL::Query](rdoc-ref:GraphQL::Query) constructor for arguments. + # + # `query_str` may be a query string; alternatively pass `document:` with a + # parsed document. The common options are `variables:`, `context:`, + # `root_value:`, `operation_name:`, `validate:`, `max_depth:`, and + # `max_complexity:`. The returned result can be serialized directly as JSON. + # + # **Returns** + # + # - `GraphQL::Query::Result` — query result, ready to be serialized as JSON + # + # :call-seq: + # execute(query_str, **kwargs) -> GraphQL::Query::Result def execute(query_str = nil, **kwargs) if default_execution_next execute_next(query_str, **kwargs) @@ -1614,24 +2109,42 @@ def execute_legacy(query_str = nil, **kwargs) # 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_all} for multiplex keyword arguments - # @param queries [Array] Keyword arguments for each query - # @option kwargs [Hash] :context ({}) Multiplex-level context - # @option kwargs [nil, Integer] :max_complexity (nil) - # @return [Array] One result for each query in the input + # See the [GraphQL::Query](rdoc-ref:GraphQL::Query) constructor for query keyword arguments. + # Multiplex-level execution is handled by the interpreter's + # `GraphQL::Execution::Interpreter.run_all` method. + # + # **Examples** + # + # **Example: Run several queries at once** + # + # ```ruby + # 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], + # } + # ``` + # + # **Parameters** + # + # - `queries` (`Array`) — Keyword arguments for each query + # + # **Options** + # + # - `kwargs.:context` (`Hash`) — ({}) Multiplex-level context + # - `kwargs.:max_complexity` (`nil, Integer`) — (nil) + # + # **Returns** + # + # - `Array` — One result for each query in the input + # + # :call-seq: + # multiplex(Array[Hash] queries, **kwargs) -> Array[GraphQL::Query::Result] def multiplex(queries, **kwargs) if @default_execution_next multiplex_next(queries, **kwargs) @@ -1659,8 +2172,7 @@ def instrumenters end end - # @api private - def add_subscription_extension_if_necessary + def add_subscription_extension_if_necessary # :nodoc: # TODO: when there's a proper API for extending root types, migrat this to use it. if !defined?(@subscription_extension_added) && @subscription_object.is_a?(Class) && self.subscriptions @subscription_extension_added = true @@ -1674,9 +2186,18 @@ def add_subscription_extension_if_necessary # Called when execution encounters a `SystemStackError`. By default, it adds a client-facing error to the response. # You could modify this method to report this error to your bug tracker. - # @param query [GraphQL::Query] - # @param err [SystemStackError] - # @return [void] + # + # **Parameters** + # + # - `query` (`GraphQL::Query`) + # - `err` (`SystemStackError`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # query_stack_error(GraphQL::Query query, SystemStackError err) -> void def query_stack_error(query, err) query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute.")) end @@ -1684,8 +2205,7 @@ def query_stack_error(query, err) # 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) + def after_lazy(value, &block) # :nodoc: if lazy?(value) GraphQL::Execution::Lazy.new do result = sync_lazy(value) @@ -1698,10 +2218,15 @@ def after_lazy(value, &block) 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) + # + # **Parameters** + # + # - `value` (`Object`) — an instance of a class registered with [.lazy_resolve](rdoc-ref:.lazy_resolve) + # + # **Returns** + # + # - `Object` — A GraphQL-ready (non-lazy) object + def sync_lazy(value) # :nodoc: lazy_method = lazy_method_name(value) if lazy_method synced_value = value.public_send(lazy_method) @@ -1711,21 +2236,33 @@ def sync_lazy(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}. + # **Returns** + # + # - `Symbol, nil` — The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with [.lazy_resolve](rdoc-ref:.lazy_resolve). + # + # :call-seq: + # lazy_method_name(obj) -> Symbol | nil def lazy_method_name(obj) lazy_methods.get(obj) end - # @return [Boolean] True if this object should be lazily resolved + # **Returns** + # + # - `Boolean` — True if this object should be lazily resolved + # + # :call-seq: + # lazy?(obj) -> bool 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) + # + # **Parameters** + # + # - `maybe_lazies` (`Array`) + def after_any_lazies(maybe_lazies) # :nodoc: if maybe_lazies.any? { |l| lazy?(l) } GraphQL::Execution::Lazy.all(maybe_lazies).then do |result| yield result @@ -1757,10 +2294,19 @@ def did_you_mean(new_dym = NOT_CONFIGURED) # If you need to support previous, non-spec behavior which allowed selecting union fields # but *not* selecting any fields on that union, set this to `true` to continue allowing that behavior. # - # If this is `true`, then {.legacy_invalid_empty_selections_on_union_with_type} will be called with {Query} objects + # If this is `true`, then [.legacy_invalid_empty_selections_on_union_with_type](rdoc-ref:.legacy_invalid_empty_selections_on_union_with_type) will be called with [Query](rdoc-ref:Query) objects # with that kind of selections. You must implement that method - # @param new_value [Boolean] - # @return [true, false, nil] + # + # **Parameters** + # + # - `new_value` (`Boolean`) + # + # **Returns** + # + # - `true, false, nil` + # + # :call-seq: + # allow_legacy_invalid_empty_selections_on_union(bool new_value) -> true | false | nil def allow_legacy_invalid_empty_selections_on_union(new_value = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_value) if defined?(@allow_legacy_invalid_empty_selections_on_union) @@ -1781,10 +2327,19 @@ def allow_legacy_invalid_empty_selections_on_union(new_value = NOT_CONFIGURED) # You should implement this method or `legacy_invalid_empty_selections_on_union_with_type` # to log the violation so that you can contact clients and notify them about changing their queries. # Then return a suitable value to tell GraphQL-Ruby how to continue. - # @param query [GraphQL::Query] - # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query - # @return [String] A validation error to return for this query - # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute) + # + # **Parameters** + # + # - `query` (`GraphQL::Query`) + # + # **Returns** + # + # - `:return_validation_error` — Let GraphQL-Ruby return the (new) normal validation error for this query + # - `String` — A validation error to return for this query + # - `nil` — Don't send the client an error, continue the legacy behavior (allow this query to execute) + # + # :call-seq: + # legacy_invalid_empty_selections_on_union(GraphQL::Query query) -> :return_validation_error | String | nil def legacy_invalid_empty_selections_on_union(query) raise "Implement `def self.legacy_invalid_empty_selections_on_union_with_type(query, type)` or `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario" end @@ -1795,11 +2350,20 @@ def legacy_invalid_empty_selections_on_union(query) # You should implement this method to log the violation so that you can contact clients # and notify them about changing their queries. Then return a suitable value to # tell GraphQL-Ruby how to continue. - # @param query [GraphQL::Query] - # @param type [Module] A GraphQL type definition - # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query - # @return [String] A validation error to return for this query - # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute) + # + # **Parameters** + # + # - `query` (`GraphQL::Query`) + # - `type` (`Module`) — A GraphQL type definition + # + # **Returns** + # + # - `:return_validation_error` — Let GraphQL-Ruby return the (new) normal validation error for this query + # - `String` — A validation error to return for this query + # - `nil` — Don't send the client an error, continue the legacy behavior (allow this query to execute) + # + # :call-seq: + # legacy_invalid_empty_selections_on_union_with_type(GraphQL::Query query, Module type) -> :return_validation_error | String | nil def legacy_invalid_empty_selections_on_union_with_type(query, type) legacy_invalid_empty_selections_on_union(query) end @@ -1809,10 +2373,18 @@ def legacy_invalid_empty_selections_on_union_with_type(query, type) # # When set to `false`, GraphQL-Ruby will reject those queries with a validation error (as per the GraphQL spec). # - # When set to `true`, GraphQL-Ruby will call {.legacy_invalid_return_type_conflicts} when the scenario is encountered. + # When set to `true`, GraphQL-Ruby will call [.legacy_invalid_return_type_conflicts](rdoc-ref:.legacy_invalid_return_type_conflicts) when the scenario is encountered. # - # @param new_value [Boolean] `true` permits the legacy behavior, `false` rejects it. - # @return [true, false, nil] + # **Parameters** + # + # - `new_value` (`Boolean`) — `true` permits the legacy behavior, `false` rejects it. + # + # **Returns** + # + # - `true, false, nil` + # + # :call-seq: + # allow_legacy_invalid_return_type_conflicts(bool new_value) -> true | false | nil def allow_legacy_invalid_return_type_conflicts(new_value = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_value) if defined?(@allow_legacy_invalid_return_type_conflicts) @@ -1833,14 +2405,22 @@ def allow_legacy_invalid_return_type_conflicts(new_value = NOT_CONFIGURED) # (Changing the field return type would be a breaking change, but if it works for your client use cases, # that might work, too.) # - # @param query [GraphQL::Query] - # @param type1 [Module] A GraphQL type definition - # @param type2 [Module] A GraphQL type definition - # @param node1 [GraphQL::Language::Nodes::Field] This node is recognized as conflicting. You might call `.line` and `.col` for custom error reporting. - # @param node2 [GraphQL::Language::Nodes::Field] The other node recognized as conflicting. - # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query - # @return [String] A validation error to return for this query - # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute) + # **Parameters** + # + # - `query` (`GraphQL::Query`) + # - `type1` (`Module`) — A GraphQL type definition + # - `type2` (`Module`) — A GraphQL type definition + # - `node1` (`GraphQL::Language::Nodes::Field`) — This node is recognized as conflicting. You might call `.line` and `.col` for custom error reporting. + # - `node2` (`GraphQL::Language::Nodes::Field`) — The other node recognized as conflicting. + # + # **Returns** + # + # - `:return_validation_error` — Let GraphQL-Ruby return the (new) normal validation error for this query + # - `String` — A validation error to return for this query + # - `nil` — Don't send the client an error, continue the legacy behavior (allow this query to execute) + # + # :call-seq: + # legacy_invalid_return_type_conflicts(GraphQL::Query query, Module type1, Module type2, GraphQL::Language::Nodes::Field node1, GraphQL::Language::Nodes::Field node2) -> :return_validation_error | String | nil def legacy_invalid_return_type_conflicts(query, type1, type2, node1, node2) raise "Implement #{self}.legacy_invalid_return_type_conflicts to handle this invalid selection" end @@ -1851,20 +2431,31 @@ def legacy_invalid_return_type_conflicts(query, type1, type2, node1, node2) # - In some cases, it called field complexity hooks repeatedly (when it should have only called them once) # # The future implementation may produce higher total complexity scores, so it's not active by default yet. You can opt into - # the future default behavior by configuring `:future` here. Or, you can choose a mode for each query with {.complexity_cost_calculation_mode_for}. + # the future default behavior by configuring `:future` here. Or, you can choose a mode for each query with [.complexity_cost_calculation_mode_for](rdoc-ref:.complexity_cost_calculation_mode_for). # # The legacy mode is currently maintained alongside the future one, but it will be removed in a future GraphQL-Ruby version. # - # If you choose `:compare`, you must also implement {.legacy_complexity_cost_calculation_mismatch} to handle the input somehow. + # If you choose `:compare`, you must also implement [.legacy_complexity_cost_calculation_mismatch](rdoc-ref:.legacy_complexity_cost_calculation_mismatch) to handle the input somehow. + # + # **Examples** + # + # **Example: Opting into the future calculation mode** # - # @example Opting into the future calculation mode - # complexity_cost_calculation_mode(:future) + # ```ruby + # complexity_cost_calculation_mode(:future) + # ``` # - # @example Choosing the legacy mode (which will work until that mode is removed...) - # complexity_cost_calculation_mode(:legacy) + # **Example: Choosing the legacy mode (which will work until that mode is removed...)** # - # @example Run both modes for every query, call {.legacy_complexity_cost_calculation_mismatch} when they don't match: - # complexity_cost_calculation_mode(:compare) + # ```ruby + # complexity_cost_calculation_mode(:legacy) + # ``` + # + # **Example: Run both modes for every query, call {.legacy_complexity_cost_calculation_mismatch} when they don't match:** + # + # ```ruby + # complexity_cost_calculation_mode(:compare) + # ``` def complexity_cost_calculation_mode(new_mode = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_mode) if defined?(@complexity_cost_calculation_mode) @@ -1882,50 +2473,81 @@ def complexity_cost_calculation_mode(new_mode = NOT_CONFIGURED) # This is a way to check the compatibility of queries coming to your API without adding overhead of running `:compare` # for every query. You could sample traffic, turn it off/on with feature flags, or anything else. # - # @example Sampling traffic - # def self.complexity_cost_calculation_mode_for(_context) - # if rand < 0.1 # 10% of the time - # :compare - # else - # :legacy - # end + # **Examples** + # + # **Example: Sampling traffic** + # + # ```ruby + # def self.complexity_cost_calculation_mode_for(_context) + # if rand < 0.1 # 10% of the time + # :compare + # else + # :legacy # end + # end + # ``` # - # @example Using a feature flag to manage future mode - # def complexity_cost_calculation_mode_for(context) - # current_user = context[:current_user] - # if Flipper.enabled?(:future_complexity_cost, current_user) - # :future - # elsif rand < 0.5 # 50% - # :compare - # else - # :legacy - # end + # **Example: Using a feature flag to manage future mode** + # + # ```ruby + # def complexity_cost_calculation_mode_for(context) + # current_user = context[:current_user] + # if Flipper.enabled?(:future_complexity_cost, current_user) + # :future + # elsif rand < 0.5 # 50% + # :compare + # else + # :legacy # end + # end + # ``` + # + # **Parameters** + # + # - `multiplex_context` (`Hash`) — The context for the currently-running `Execution::Multiplex` (which contains one or more queries) + # + # **Returns** + # + # - `:future` — Use the new calculation algorithm -- may be higher than `:legacy` + # - `:legacy` — Use the legacy calculation algorithm, warts and all + # - `:compare` — Run both algorithms and call [.legacy_complexity_cost_calculation_mismatch](rdoc-ref:.legacy_complexity_cost_calculation_mismatch) if they don't match # - # @param multiplex_context [Hash] The context for the currently-running {Execution::Multiplex} (which contains one or more queries) - # @return [:future] Use the new calculation algorithm -- may be higher than `:legacy` - # @return [:legacy] Use the legacy calculation algorithm, warts and all - # @return [:compare] Run both algorithms and call {.legacy_complexity_cost_calculation_mismatch} if they don't match + # :call-seq: + # complexity_cost_calculation_mode_for(Hash multiplex_context) -> :future | :legacy | :compare def complexity_cost_calculation_mode_for(multiplex_context) complexity_cost_calculation_mode end # Implement this method in your schema to handle mismatches when `:compare` is used. # - # @example Logging the mismatch - # def self.legacy_cost_calculation_mismatch(multiplex, future_cost, legacy_cost) - # client_id = multiplex.context[:api_client].id - # operation_names = multiplex.queries.map { |q| q.selected_operation_name || "anonymous" }.join(", ") - # Stats.increment(:complexity_mismatch, tags: { client: client_id, ops: operation_names }) - # legacy_cost - # end - # @see Query::Context#add_error Adding an error to the response to notify the client - # @see Query::Context#response_extensions Adding key-value pairs to the response `"extensions" => { ... }` - # @param multiplex [GraphQL::Execution::Multiplex] - # @param future_complexity_cost [Integer] - # @param legacy_complexity_cost [Integer] - # @return [Integer] the cost to use for this query (probably one of `future_complexity_cost` or `legacy_complexity_cost`) + # See [Query::Context#add_error](rdoc-ref:Query::Context#add_error) Adding an error to the response to notify the client + # See [Query::Context#response_extensions](rdoc-ref:Query::Context#response_extensions) Adding key-value pairs to the response `"extensions" => { ... }` + # + # **Examples** + # + # **Example: Logging the mismatch** + # + # ```ruby + # def self.legacy_cost_calculation_mismatch(multiplex, future_cost, legacy_cost) + # client_id = multiplex.context[:api_client].id + # operation_names = multiplex.queries.map { |q| q.selected_operation_name || "anonymous" }.join(", ") + # Stats.increment(:complexity_mismatch, tags: { client: client_id, ops: operation_names }) + # legacy_cost + # end + # ``` + # + # **Parameters** + # + # - `multiplex` (`GraphQL::Execution::Multiplex`) + # - `future_complexity_cost` (`Integer`) + # - `legacy_complexity_cost` (`Integer`) + # + # **Returns** + # + # - `Integer` — the cost to use for this query (probably one of `future_complexity_cost` or `legacy_complexity_cost`) + # + # :call-seq: + # legacy_complexity_cost_calculation_mismatch(GraphQL::Execution::Multiplex multiplex, Integer future_complexity_cost, Integer legacy_complexity_cost) -> Integer def legacy_complexity_cost_calculation_mismatch(multiplex, future_complexity_cost, legacy_complexity_cost) raise "Implement #{self}.legacy_complexity_cost(multiplex, future_complexity_cost, legacy_complexity_cost) to handle this mismatch (#{future_complexity_cost} vs. #{legacy_complexity_cost}) and return a value to use" end @@ -1947,8 +2569,16 @@ def add_trace_options_for(mode, new_options) nil end - # @param t [Module, Array] - # @return [void] + # **Parameters** + # + # - `t` (`Module, Array`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # add_type_and_traverse(Module | Array[Module] t, root:) -> void def add_type_and_traverse(t, root:) if root @root_types ||= [] @@ -2088,8 +2718,7 @@ def get_references_to(type_defn) # Install these here so that subclasses will also install it. self.connections = GraphQL::Pagination::Connections.new(schema: self) - # @api private - module DefaultTraceClass + module DefaultTraceClass # :nodoc: end end end diff --git a/lib/graphql/schema/argument.rb b/lib/graphql/schema/argument.rb index 214d6c6ef91..ad9ce9b73be 100644 --- a/lib/graphql/schema/argument.rb +++ b/lib/graphql/schema/argument.rb @@ -1,6 +1,178 @@ # frozen_string_literal: true module GraphQL class Schema + # Arguments describe the input accepted by a field or input object. + # This API reference was migrated from guides/fields/arguments.md. + # Keep argument-specific behavior and examples here; the guide is only + # an entry point for the broader fields documentation. + # + # 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 + # end + # + # def search_posts(category:) + # Post.where(category: category).limit(10) + # end + # ``` + # + # ## Nullability + # + # 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 + # ``` + # + # ### Default Values + # + # 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 + # ``` + # + # Arguments with `required: false` _do_ accept `null` as inputs from clients. This can be surprising in resolver code, for example, an argument with `Integer, required: false` can sometimes be `nil`. In this case, you can use `replace_null_with_default: true` to apply the given `default_value: ...` when clients provide `null`. For example: + # + # ```ruby + # # Even if clients send `query: null`, the resolver will receive `"*"` for this argument: + # argument :query, String, required: false, default_value: "*", replace_null_with_default: true + # ``` + # + # Finally, `required: :nullable` will require clients to pass the argument, although it will accept `null` as a valid input. For example: + # + # ```ruby + # # This argument _must_ be given -- send `null` if there's no other appropriate value: + # argument :email_address, String, required: :nullable + # ``` + # + # + # ## Deprecation + # + # **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 + # ``` + # + # ## Aliasing + # + # 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, as: :id + # end + # + # def post(id:) + # Post.find(id) + # end + # ``` + # + # ## Preprocessing + # + # 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, 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 + # ``` + # + # ## Automatic camelization + # + # 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 + # 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, 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 + # end + # + # def posts(start_year:) + # # ... + # end + # ``` + # + # ## Valid Argument Types + # + # Only certain types are valid for arguments: + # + # - [GraphQL::Schema::Scalar](rdoc-ref:GraphQL::Schema::Scalar), including built-in scalars (string, int, float, boolean, ID) + # - [GraphQL::Schema::Enum](rdoc-ref:GraphQL::Schema::Enum) + # - [GraphQL::Schema::InputObject](rdoc-ref:GraphQL::Schema::InputObject), which allows key-value pairs as input + # - [GraphQL::Schema::List](rdoc-ref:GraphQL::Schema::List)s of a valid input type, configured using `[...]` + # - [GraphQL::Schema::NonNull](rdoc-ref:GraphQL::Schema::NonNull)s of a valid input type (arguments are non-null by default; use `required: false` to make optional arguments) + # class Argument include GraphQL::Schema::Member::HasPath include GraphQL::Schema::Member::HasAstNode @@ -10,15 +182,33 @@ class Argument include GraphQL::Schema::Member::HasValidators include GraphQL::EmptyObjects - # @return [String] the GraphQL name for this argument, camelized unless `camelize: false` is provided + # **Returns** + # + # - `String` — the GraphQL name for this argument, camelized unless `camelize: false` is provided + # + # :call-seq: + # name -> String attr_reader :name alias :graphql_name :name - # @return [GraphQL::Schema::Field, Class] The field or input object this argument belongs to + # **Returns** + # + # - `GraphQL::Schema::Field, Class` — The field or input object this argument belongs to + # + # :call-seq: + # owner -> GraphQL::Schema::Field | Class attr_reader :owner - # @param new_prepare [Method, Proc] - # @return [Symbol] A method or proc to call to transform this value before sending it to field resolution method + # **Parameters** + # + # - `new_prepare` (`Method, Proc`) + # + # **Returns** + # + # - `Symbol` — A method or proc to call to transform this value before sending it to field resolution method + # + # :call-seq: + # prepare(Method | Proc new_prepare) -> Symbol def prepare(new_prepare = NOT_CONFIGURED) if new_prepare != NOT_CONFIGURED @prepare = new_prepare @@ -26,38 +216,58 @@ def prepare(new_prepare = NOT_CONFIGURED) @prepare end - # @return [Symbol] This argument's name in Ruby keyword arguments + # **Returns** + # + # - `Symbol` — This argument's name in Ruby keyword arguments + # + # :call-seq: + # keyword -> Symbol attr_reader :keyword - # @return [Class, Module, nil] If this argument should load an application object, this is the type of object to load + # **Returns** + # + # - `Class, Module, nil` — If this argument should load an application object, this is the type of object to load + # + # :call-seq: + # loads -> Class | Module | nil attr_reader :loads - # @return [Boolean] true if a resolver defined this argument + # **Returns** + # + # - `Boolean` — true if a resolver defined this argument + # + # :call-seq: + # from_resolver?() -> bool def from_resolver? @from_resolver end - # @param arg_name [Symbol] - # @param type_expr - # @param desc [String] - # @param type [Class, Array] Input type; positional argument also accepted - # @param name [Symbol] positional argument also accepted # @param loads [Class, Array] A GraphQL type to load for the given ID when one is present - # @param definition_block [Proc] Called with the newly-created {Argument} - # @param owner [Class] Private, used by GraphQL-Ruby during schema definition - # @param required [Boolean, :nullable] if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`. - # @param description [String] - # @param default_value [Object] - # @param loads [Class, Array] A GraphQL type to load for the given ID when one is present - # @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 directives [Hash{Class => Hash}] - # @param deprecation_reason [String] - # @param validates [Hash, nil] Options for building validators, if any should be applied - # @param replace_null_with_default [Boolean] if `true`, incoming values of `null` will be replaced with the configured `default_value` - # @param comment [String] Private, used by GraphQL-Ruby when parsing GraphQL schema files - # @param ast_node [GraphQL::Language::Nodes::InputValueDefinition] Private, used by GraphQL-Ruby when parsing schema files + # **Parameters** + # + # - `arg_name` (`Symbol`) + # - `type_expr` + # - `desc` (`String`) + # - `type` (`Class, Array`) — Input type; positional argument also accepted + # - `name` (`Symbol`) — positional argument also accepted + # - `definition_block` (`Proc`) — Called with the newly-created [Argument](rdoc-ref:Argument) + # - `owner` (`Class`) — Private, used by GraphQL-Ruby during schema definition + # - `required` (`Boolean, :nullable`) — if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`. + # - `description` (`String`) + # - `default_value` (`Object`) + # - `loads` (`Class, Array`) — A GraphQL type to load for the given ID when one is present + # - `as` (`Symbol`) — Override the keyword name when passed to a method + # - `prepare` (`Symbol`) — A method to call to transform this argument's valuebefore sending it to field resolution + # - `camelize` (`Boolean`) — if true, the name will be camelized when building the schema + # - `from_resolver` (`Boolean`) — if true, a Resolver class defined this argument + # - `directives` (`Hash{Class => Hash}`) + # - `deprecation_reason` (`String`) + # - `validates` (`Hash, nil`) — Options for building validators, if any should be applied + # - `replace_null_with_default` (`Boolean`) — if `true`, incoming values of `null` will be replaced with the configured `default_value` + # - `comment` (`String`) — Private, used by GraphQL-Ruby when parsing GraphQL schema files + # - `ast_node` (`GraphQL::Language::Nodes::InputValueDefinition`) — Private, used by GraphQL-Ruby when parsing schema files + # + # :call-seq: + # initialize(Symbol arg_name, type_expr, String desc, bool | :nullable required:, Class | Array[Class] type:, Symbol name:, Class | Array[Class] loads:, String description:, String comment:, GraphQL::Language::Nodes::InputValueDefinition ast_node:, Object default_value:, Symbol as:, bool from_resolver:, bool camelize:, Symbol prepare:, Class owner:, Hash | nil validates:, Hash[Class, Hash] directives:, String deprecation_reason:, bool replace_null_with_default:, Proc &definition_block) def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, comment: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block) arg_name ||= name @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s) @@ -107,8 +317,16 @@ def inspect "#<#{self.class} #{path}: #{type.to_type_signature}#{description ? " @description=#{description.inspect}" : ""}>" end - # @param default_value [Object] The value to use when the client doesn't provide one - # @return [Object] the value used when the client doesn't provide a value for this argument + # **Parameters** + # + # - `default_value` (`Object`) — The value to use when the client doesn't provide one + # + # **Returns** + # + # - `Object` — the value used when the client doesn't provide a value for this argument + # + # :call-seq: + # default_value(new_default_value) -> Object def default_value(new_default_value = NOT_CONFIGURED) if new_default_value != NOT_CONFIGURED @default_value = new_default_value @@ -116,7 +334,12 @@ def default_value(new_default_value = NOT_CONFIGURED) @default_value end - # @return [Boolean] True if this argument has a default value + # **Returns** + # + # - `Boolean` — True if this argument has a default value + # + # :call-seq: + # default_value?() -> bool def default_value? @default_value != NOT_CONFIGURED end @@ -127,7 +350,12 @@ def replace_null_with_default? attr_writer :description - # @return [String] Documentation for this argument + # **Returns** + # + # - `String` — Documentation for this argument + # + # :call-seq: + # description(text) -> String def description(text = nil) if text @description = text @@ -138,7 +366,12 @@ def description(text = nil) attr_writer :comment - # @return [String] Comment for this argument + # **Returns** + # + # - `String` — Comment for this argument + # + # :call-seq: + # comment(text) -> String def comment(text = nil) if text @comment = text @@ -147,7 +380,12 @@ def comment(text = nil) end end - # @return [String] Deprecation reason for this argument + # **Returns** + # + # - `String` — Deprecation reason for this argument + # + # :call-seq: + # deprecation_reason(text) -> String def deprecation_reason(text = nil) if text self.deprecation_reason = text @@ -229,10 +467,9 @@ def freeze super end - # Apply the {prepare} configuration to `value`, using methods from `obj`. + # Apply the [prepare](rdoc-ref:prepare) configuration to `value`, using methods from `obj`. # Used by the runtime. - # @api private - def prepare_value(obj, value, context: nil) + def prepare_value(obj, value, context: nil) # :nodoc: if type.unwrap.kind.input_object? value = recursively_prepare_input_object(value, type, context) end @@ -263,8 +500,7 @@ def prepare_value(obj, value, context: nil) end end - # @api private - def coerce_into_values(parent_object, values, context, argument_values) + def coerce_into_values(parent_object, values, context, argument_values) # :nodoc: arg_name = graphql_name arg_key = keyword default_used = false @@ -377,8 +613,7 @@ def load_and_authorize_value(load_method_owner, coerced_value, context) end end - # @api private - def validate_default_value + def validate_default_value # :nodoc: return unless default_value? coerced_default_value = begin # This is weird, but we should accept single-item default values for list-type arguments. diff --git a/lib/graphql/schema/base_64_encoder.rb b/lib/graphql/schema/base_64_encoder.rb index 3679465460e..90679651ec4 100644 --- a/lib/graphql/schema/base_64_encoder.rb +++ b/lib/graphql/schema/base_64_encoder.rb @@ -2,8 +2,7 @@ require "base64" module GraphQL class Schema - # @api private - module Base64Encoder + module Base64Encoder # :nodoc: def self.encode(unencoded_text, nonce: false) Base64.urlsafe_encode64(unencoded_text, padding: false) end diff --git a/lib/graphql/schema/build_from_definition.rb b/lib/graphql/schema/build_from_definition.rb index 0490eaa5346..b226431b32d 100644 --- a/lib/graphql/schema/build_from_definition.rb +++ b/lib/graphql/schema/build_from_definition.rb @@ -5,7 +5,7 @@ module GraphQL class Schema module BuildFromDefinition class << self - # @see {Schema.from_definition} + # See [Schema.from_definition](rdoc-ref:Schema.from_definition) def from_definition(schema_superclass, definition_string, parser: GraphQL.default_parser, **kwargs) if defined?(parser::SchemaParser) parser = parser::SchemaParser @@ -25,8 +25,7 @@ def from_document(schema_superclass, document, default_resolve:, using: {}, base end end - # @api private - module Builder + module Builder # :nodoc: include GraphQL::EmptyObjects extend self @@ -293,7 +292,13 @@ def build_definition_from_node(definition, type_resolver, default_resolve, base_ # with their actual definitions. # # (Schema definitions are allowed to reference those built-ins without redefining them.) - # @return void + # + # **Returns** + # + # - `Object` — void + # + # :call-seq: + # replace_late_bound_types_with_built_in(types) -> Object def replace_late_bound_types_with_built_in(types) GraphQL::Schema::BUILT_IN_TYPES.each do |scalar_name, built_in_scalar| existing_type = types[scalar_name] diff --git a/lib/graphql/schema/build_from_definition/resolve_map.rb b/lib/graphql/schema/build_from_definition/resolve_map.rb index 4125d17a435..a5dcb276246 100644 --- a/lib/graphql/schema/build_from_definition/resolve_map.rb +++ b/lib/graphql/schema/build_from_definition/resolve_map.rb @@ -12,8 +12,7 @@ module BuildFromDefinition # # Interface/union resolution can be provided as a `resolve_type:` key. # - # @api private - class ResolveMap + class ResolveMap # :nodoc: module NullScalarCoerce def self.call(val, _ctx) val diff --git a/lib/graphql/schema/directive.rb b/lib/graphql/schema/directive.rb index baa47515da6..038c4c9c754 100644 --- a/lib/graphql/schema/directive.rb +++ b/lib/graphql/schema/directive.rb @@ -122,10 +122,20 @@ def inherited(subclass) end end - # @return [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class, Module] + # **Returns** + # + # - `GraphQL::Schema::Field, GraphQL::Schema::Argument, Class, Module` + # + # :call-seq: + # owner -> GraphQL::Schema::Field | GraphQL::Schema::Argument | Class | Module attr_reader :owner - # @return [GraphQL::Interpreter::Arguments] + # **Returns** + # + # - `GraphQL::Interpreter::Arguments` + # + # :call-seq: + # arguments -> GraphQL::Interpreter::Arguments attr_reader :arguments class InvalidArgumentError < GraphQL::Error diff --git a/lib/graphql/schema/directive/feature.rb b/lib/graphql/schema/directive/feature.rb index 94aea2d7e7d..51f8934dba7 100644 --- a/lib/graphql/schema/directive/feature.rb +++ b/lib/graphql/schema/directive/feature.rb @@ -12,27 +12,35 @@ class Directive < GraphQL::Schema::Member # # 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 + # **Examples** + # + # **Example: Implementing the Feature directive** + # + # ```ruby + # # 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 - # } + # **Example: Flagging a part of the query** + # + # ```ruby + # 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." @@ -53,10 +61,18 @@ def self.include?(object, arguments, context) # 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 + # **Parameters** + # + # - `flag_name` (`String`) — The client-provided string of a feature to check + # - `object` (`GraphQL::Schema::Objct`) — The currently-evaluated GraphQL object instance + # - `context` (`GraphQL::Query::Context`) + # + # **Returns** + # + # - `Boolean` — If truthy, execution will continue + # + # :call-seq: + # enabled?(String flag_name, GraphQL::Schema::Objct object, GraphQL::Query::Context context) -> bool 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 diff --git a/lib/graphql/schema/directive/transform.rb b/lib/graphql/schema/directive/transform.rb index 1ad15912152..39a10fc1ec2 100644 --- a/lib/graphql/schema/directive/transform.rb +++ b/lib/graphql/schema/directive/transform.rb @@ -8,15 +8,23 @@ class Directive < GraphQL::Schema::Member # 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 + # **Examples** # - # @example Transforming strings - # viewer { - # username @transform(by: "upcase") - # } + # **Example: Installing the directive** + # + # ```ruby + # class MySchema < GraphQL::Schema + # directive(GraphQL::Schema::Directive::Transform) + # end + # ``` + # + # **Example: Transforming strings** + # + # ```ruby + # viewer { + # username @transform(by: "upcase") + # } + # ``` class Transform < Schema::Directive description "Directs the executor to run named transform on the return value." diff --git a/lib/graphql/schema/enum.rb b/lib/graphql/schema/enum.rb index 4be001fe92c..354cf6d7d2a 100644 --- a/lib/graphql/schema/enum.rb +++ b/lib/graphql/schema/enum.rb @@ -7,18 +7,22 @@ class 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::Schema::Enum - # value :MUSHROOMS - # value :ONIONS - # value :PEPPERS - # end + # **Examples** + # + # **Example: # equivalent to** + # + # ```ruby + # # enum PizzaTopping { + # # MUSHROOMS + # # ONIONS + # # PEPPERS + # # } + # class PizzaTopping < GraphQL::Schema::Enum + # value :MUSHROOMS + # value :ONIONS + # value :PEPPERS + # end + # ``` class Enum < GraphQL::Schema::Member extend GraphQL::Schema::Member::ValidatesInput @@ -57,15 +61,27 @@ def initialize(enum_type) class << self # Define a value for this enum - # @option kwargs [String, Symbol] :graphql_name the GraphQL value for this, usually `SCREAMING_CASE` - # @option kwargs [String] :description, the GraphQL description for this value, present in documentation - # @option kwargs [String] :comment, the GraphQL comment for this value, present in documentation - # @option kwargs [::Object] :value the translated Ruby value for this object (defaults to `graphql_name`) - # @option kwargs [::Object] :value_method, the method name to fetch `graphql_name` (defaults to `graphql_name.downcase`) - # @option kwargs [String] :deprecation_reason if this object is deprecated, include a message here - # @param value_method [Symbol, false] A method to generate for this value, or `false` to skip generation - # @return [void] - # @see {Schema::EnumValue} which handles these inputs by default + # See [Schema::EnumValue](rdoc-ref:Schema::EnumValue) which handles these inputs by default + # + # **Options** + # + # - `kwargs.:graphql_name` (`String, Symbol`) — the GraphQL value for this, usually `SCREAMING_CASE` + # - `kwargs.:description,` (`String`) — the GraphQL description for this value, present in documentation + # - `kwargs.:comment,` (`String`) — the GraphQL comment for this value, present in documentation + # - `kwargs.:value` (`::Object`) — the translated Ruby value for this object (defaults to `graphql_name`) + # - `kwargs.:value_method,` (`::Object`) — the method name to fetch `graphql_name` (defaults to `graphql_name.downcase`) + # - `kwargs.:deprecation_reason` (`String`) — if this object is deprecated, include a message here + # + # **Parameters** + # + # - `value_method` (`Symbol, false`) — A method to generate for this value, or `false` to skip generation + # + # **Returns** + # + # - `void` + # + # :call-seq: + # value(*args, Symbol | false value_method:, **kwargs, &block) -> void def value(*args, value_method: nil, **kwargs, &block) kwargs[:owner] = self value = enum_value_class.new(*args, **kwargs, &block) @@ -89,7 +105,12 @@ def value(*args, value_method: nil, **kwargs, &block) value end - # @return [Array] Possible values of this enum + # **Returns** + # + # - `Array` — Possible values of this enum + # + # :call-seq: + # enum_values(context:) -> Array[GraphQL::Schema::EnumValue] def enum_values(context = GraphQL::Query::NullContext.instance) inherited_values = superclass.respond_to?(:enum_values) ? superclass.enum_values(context) : nil visible_values = [] @@ -126,7 +147,12 @@ def enum_values(context = GraphQL::Query::NullContext.instance) visible_values end - # @return [Array] An unfiltered list of all definitions + # **Returns** + # + # - `Array` — An unfiltered list of all definitions + # + # :call-seq: + # all_enum_value_definitions() -> Array[Schema::EnumValue] def all_enum_value_definitions all_defns = if superclass.respond_to?(:all_enum_value_definitions) superclass.all_enum_value_definitions @@ -145,12 +171,22 @@ def all_enum_value_definitions all_defns end - # @return [Hash GraphQL::Schema::EnumValue>] Possible values of this enum, keyed by name. + # **Returns** + # + # - `Hash GraphQL::Schema::EnumValue>` — Possible values of this enum, keyed by name. + # + # :call-seq: + # values(context:) -> Hash[String, GraphQL::Schema::EnumValue] def values(context = GraphQL::Query::NullContext.instance) enum_values(context).each_with_object({}) { |val, obj| obj[val.graphql_name] = val } end - # @return [Class] for handling `value(...)` inputs and building `GraphQL::Enum::EnumValue`s out of them + # **Returns** + # + # - `Class` — for handling `value(...)` inputs and building `GraphQL::Enum::EnumValue`s out of them + # + # :call-seq: + # enum_value_class(new_enum_value_class) -> Class def enum_value_class(new_enum_value_class = nil) if new_enum_value_class @enum_value_class = new_enum_value_class @@ -191,11 +227,23 @@ def validate_non_null_input(value_name, ctx, max_errors: nil) end # Called by the runtime when a field returns a value to give back to the client. - # This method checks that the incoming {value} matches one of the enum's defined values. - # @param value [Object] Any value matching the values for this enum. - # @param ctx [GraphQL::Query::Context] - # @raise [GraphQL::Schema::Enum::UnresolvedValueError] if {value} doesn't match a configured value or if the matching value isn't authorized. - # @return [String] The GraphQL-ready string for {value} + # This method checks that the incoming [value](rdoc-ref:value) matches one of the enum's defined values. + # + # **Parameters** + # + # - `value` (`Object`) — Any value matching the values for this enum. + # - `ctx` (`GraphQL::Query::Context`) + # + # **Raises** + # + # - `GraphQL::Schema::Enum::UnresolvedValueError` — if [value](rdoc-ref:value) doesn't match a configured value or if the matching value isn't authorized. + # + # **Returns** + # + # - `String` — The GraphQL-ready string for [value](rdoc-ref:value) + # + # :call-seq: + # coerce_result(Object value, GraphQL::Query::Context ctx) -> String def coerce_result(value, ctx) types = ctx.types all_values = types ? types.enum_values(self) : values.each_value @@ -209,10 +257,22 @@ def coerce_result(value, ctx) # Called by the runtime with incoming string representations from a query. # It will match the string to a configured by name or by Ruby value. - # @param value_name [String, Object] A string from a GraphQL query, or a Ruby value matching a `value(..., value: ...)` configuration - # @param ctx [GraphQL::Query::Context] - # @raise [GraphQL::UnauthorizedEnumValueError] if an {EnumValue} matches but returns false for `.authorized?`. Goes to {Schema.unauthorized_object}. - # @return [Object] The Ruby value for the matched {GraphQL::Schema::EnumValue} + # + # **Parameters** + # + # - `value_name` (`String, Object`) — A string from a GraphQL query, or a Ruby value matching a `value(..., value: ...)` configuration + # - `ctx` (`GraphQL::Query::Context`) + # + # **Raises** + # + # - `GraphQL::UnauthorizedEnumValueError` — if an [EnumValue](rdoc-ref:EnumValue) matches but returns false for `.authorized?`. Goes to [Schema.unauthorized_object](rdoc-ref:Schema.unauthorized_object). + # + # **Returns** + # + # - `Object` — The Ruby value for the matched [GraphQL::Schema::EnumValue](rdoc-ref:GraphQL::Schema::EnumValue) + # + # :call-seq: + # coerce_input(String | Object value_name, GraphQL::Query::Context ctx) -> Object def coerce_input(value_name, ctx) all_values = ctx.types ? ctx.types.enum_values(self) : values.each_value diff --git a/lib/graphql/schema/enum_value.rb b/lib/graphql/schema/enum_value.rb index 74484f60cb9..12082cb67aa 100644 --- a/lib/graphql/schema/enum_value.rb +++ b/lib/graphql/schema/enum_value.rb @@ -2,23 +2,28 @@ module GraphQL class Schema - # A possible value for an {Enum}. + # A possible value for an [Enum](rdoc-ref: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 - # end + # **Examples** + # + # **Example: custom enum value class** # - # class BaseEnum < GraphQL::Schema::Enum - # # use it for these enums: - # enum_value_class CustomEnumValue + # ```ruby + # # define a custom class: + # class CustomEnumValue < GraphQL::Schema::EnumValue + # def initialize(*args) + # # arguments to `value(...)` in Enum classes are passed here + # super # 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::HasPath include GraphQL::Schema::Member::HasAstNode @@ -27,7 +32,12 @@ class EnumValue < GraphQL::Schema::Member attr_reader :graphql_name - # @return [Class] The enum type that owns this value + # **Returns** + # + # - `Class` — The enum type that owns this value + # + # :call-seq: + # owner -> Class attr_reader :owner def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil, description: nil, comment: nil, value: NOT_CONFIGURED, deprecation_reason: nil, &block) diff --git a/lib/graphql/schema/field.rb b/lib/graphql/schema/field.rb index 9d284e02c69..cf003daefcf 100644 --- a/lib/graphql/schema/field.rb +++ b/lib/graphql/schema/field.rb @@ -4,6 +4,276 @@ module GraphQL class Schema + # Field definitions are the primary way to expose data from an object type. + # This API reference was migrated from guides/fields/introduction.md. + # Keep field-specific behavior and examples here; the guide is only an + # entry point for the broader fields documentation. + # + # 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 + # ``` + # + # [Objects](/type_definitions/objects) and [Interfaces](/type_definitions/interfaces) have fields. + # + # The different elements of field definition are addressed below: + # + # - [Names](#class-graphql-schema-field-field-names) identify the field in GraphQL + # - [Return types](#class-graphql-schema-field-field-return-type) say what kind of data this field returns + # - [Documentation](#class-graphql-schema-field-field-documentation) includes description, comments and deprecation notes + # - [Resolution behavior](#class-graphql-schema-field-field-resolution) hooks up the GraphQL field to Ruby code + # - [Arguments](#class-graphql-schema-field-field-arguments) allow fields to take input when they're queried + # - [Extra field metadata](#class-graphql-schema-field-extra-field-metadata) for low-level access to the GraphQL-Ruby runtime + # - [Add default values for field parameters](#class-graphql-schema-field-field-parameter-default-values) + # + # ## Field Names + # + # A field's name is provided as the first argument or as the `name:` option: + # + # ```ruby + # field :team_captain, ... + # # or: + # field ..., name: :team_captain + # ``` + # + # Under the hood, GraphQL-Ruby **camelizes** field names, so `field :team_captain, ...` would be `{ teamCaptain }` in GraphQL. You can disable this behavior by adding `camelize: false` to your field definition or to the [default field options](#class-graphql-schema-field-field-parameter-default-values). + # + # The field's name is also used as the basis of [field resolution](#class-graphql-schema-field-field-resolution). + # + # ## 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 [list type](/type_definitions/lists). + # + # [Nullability](/type_definitions/non_nulls) is expressed with the `null:` keyword: + # + # - `null: true` (default) 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 # `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] # `[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__, __comment__ 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 + # ``` + # + # __Comments__ can be added with the `field(...)` method as a keyword argument, or inside the block: + # ```ruby + # # `comment:` keyword + # field :name, String, null: false, comment: "Rename to full name" + # + # # inside the block + # field :name, String, null: false do + # comment "Rename to full name" + # end + # ``` + # + # Generates field name with comment above "Rename to full name" above. + # + # ```graphql + # type Foo { + # # Rename to full name + # name: String! + # } + # ``` + # + # __Deprecated__ fields can be marked by adding a `deprecation_reason:` keyword argument: + # + # ```ruby + # field :email, String, + # 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. + # - An optional `:fallback_value` can be supplied that will be used if the above fail. + # + # 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(s) with the `hash_key:` or `dig:` 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" + # + # # Use the `#dig` method on the hash with `:nested` and `:movies` keys + # field :movies, [Movie], null: false, + # dig: [:nested, :movies] + # ``` + # + # 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 [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 [Lookahead](/queries/lookahead)) + # - `execution_errors`, whose `#add(err_or_msg)` method should be used for adding errors + # - `argument_details` (Interpreter only), an instance of [GraphQL::Execution::Interpreter::Arguments](rdoc-ref:GraphQL::Execution::Interpreter::Arguments) with argument metadata + # - `parent` (the previous `object` in the query) + # - 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 + # ``` + # + # To use `CustomField` in your Objects and Interfaces, you'll need to register it as a `field_class` on those classes. See [Customizing Fields](https://graphql-ruby.org/type_definitions/extensions#customizing-fields) for more information on how to do so. + # class Field include GraphQL::Schema::Member::HasArguments include GraphQL::Schema::Member::HasArguments::FieldConfigured @@ -18,22 +288,42 @@ class Field class FieldImplementationFailed < GraphQL::Error; end - # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided + # **Returns** + # + # - `String` — the GraphQL name for this field, camelized unless `camelize: false` is provided + # + # :call-seq: + # name -> String attr_reader :name alias :graphql_name :name attr_writer :description - # @return [Symbol] Method or hash key on the underlying object to look up + # **Returns** + # + # - `Symbol` — Method or hash key on the underlying object to look up + # + # :call-seq: + # method_sym -> Symbol attr_reader :method_sym - # @return [String] Method or hash key on the underlying object to look up + # **Returns** + # + # - `String` — Method or hash key on the underlying object to look up + # + # :call-seq: + # method_str -> String attr_reader :method_str attr_reader :hash_key attr_reader :dig_keys - # @return [Symbol] The method on the type to look up + # **Returns** + # + # - `Symbol` — The method on the type to look up + # + # :call-seq: + # resolver_method() -> Symbol def resolver_method if @resolver_class @resolver_class.resolver_method @@ -42,7 +332,12 @@ def resolver_method end end - # @return [String, nil] + # **Returns** + # + # - `String, nil` + # + # :call-seq: + # deprecation_reason() -> String | nil def deprecation_reason super || @resolver_class&.deprecation_reason end @@ -68,10 +363,20 @@ def directives end end - # @return [Class] The thing this field was defined on (type, mutation, resolver) + # **Returns** + # + # - `Class` — The thing this field was defined on (type, mutation, resolver) + # + # :call-seq: + # owner -> Class attr_accessor :owner - # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type) + # **Returns** + # + # - `Class` — The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type) + # + # :call-seq: + # owner_type() -> Class def owner_type @owner_type ||= if owner.nil? raise GraphQL::InvariantError, "Field #{original_name.inspect} (graphql name: #{graphql_name.inspect}) has no owner, but all fields should have an owner. How did this happen?!" @@ -82,15 +387,30 @@ def owner_type end end - # @return [Symbol] the original name of the field, passed in by the user + # **Returns** + # + # - `Symbol` — the original name of the field, passed in by the user + # + # :call-seq: + # original_name -> Symbol attr_reader :original_name - # @return [Class, nil] The {Schema::Resolver} this field was derived from, if there is one + # **Returns** + # + # - `Class, nil` — The [Schema::Resolver](rdoc-ref:Schema::Resolver) this field was derived from, if there is one + # + # :call-seq: + # resolver() -> Class | nil def resolver @resolver_class end - # @return [Boolean] Is this field a predefined introspection field? + # **Returns** + # + # - `Boolean` — Is this field a predefined introspection field? + # + # :call-seq: + # introspection?() -> bool def introspection? @introspection end @@ -101,17 +421,33 @@ def inspect alias :mutation :resolver - # @return [Boolean] Apply tracing to this field? (Default: skip scalars, this is the override value) + # **Returns** + # + # - `Boolean` — Apply tracing to this field? (Default: skip scalars, this is the override value) + # + # :call-seq: + # trace -> bool attr_reader :trace - # @return [String, nil] + # **Returns** + # + # - `String, nil` + # + # :call-seq: + # subscription_scope() -> String | nil def subscription_scope @subscription_scope || (@resolver_class.respond_to?(:subscription_scope) ? @resolver_class.subscription_scope : nil) end attr_writer :subscription_scope # 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 + # + # **Returns** + # + # - `Boolean` — if true, this field will be wrapped with Relay connection behavior + # + # :call-seq: + # connection?() -> bool def connection? if @connection.nil? # Provide default based on type name @@ -134,7 +470,12 @@ def connection? end end - # @return [Boolean] if true, the return type's `.scope_items` method will be applied to this field's return value + # **Returns** + # + # - `Boolean` — if true, the return type's `.scope_items` method will be applied to this field's return value + # + # :call-seq: + # scoped?() -> bool def scoped? if !@scope.nil? # The default was overridden @@ -154,14 +495,26 @@ def scoped? end end - # This extension is applied to fields when {#connection?} is true. + # This extension is applied to fields when [connection?](rdoc-ref:#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 + # + # **Returns** + # + # - `Class` — A [FieldExtension](rdoc-ref:FieldExtension) subclass for implementing pagination behavior. + # + # **Examples** + # + # **Example: Configuring a custom extension** + # + # ```ruby + # class Types::BaseField < GraphQL::Schema::Field + # connection_extension(MyCustomExtension) + # end + # ``` + # + # :call-seq: + # connection_extension(new_extension_class) -> Class def self.connection_extension(new_extension_class = nil) if new_extension_class @connection_extension = new_extension_class @@ -170,56 +523,76 @@ def self.connection_extension(new_extension_class = nil) end end - # @return Boolean + # **Returns** + # + # - `Object` — Boolean + # + # :call-seq: + # relay_node_field -> Object attr_reader :relay_node_field - # @return Boolean + # **Returns** + # + # - `Object` — Boolean + # + # :call-seq: + # relay_nodes_field -> Object attr_reader :relay_nodes_field - # @return [Boolean] Should we warn if this field's name conflicts with a built-in method? + # **Returns** + # + # - `Boolean` — Should we warn if this field's name conflicts with a built-in method? + # + # :call-seq: + # method_conflict_warning?() -> bool 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] (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null` - # @param description [String] Field description - # @param comment [String] Field comment - # @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 dig [Array] The nested hash keys to lookup on the underlying hash to resolve this field using dig - # @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 resolve_static [Symbol, true, nil] Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments` - # @param resolve_batch [Symbol, true, nil] Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`. - # @param resolve_each [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`. - # @param resolve_legacy_instance_method [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class. - # @param dataload [Class, Hash] Shorthand for making dataloader calls - # @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 default_page_size [Integer, nil] For connections, the default number of items to return from this field, or `nil` to return unlimited results. - # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__` - # @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 directives [Hash{Class => Hash}] Directives to apply to this field - # @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 - # @param validates [Array] Configurations for validating this field - # @param fallback_value [Object] A fallback value if the method is not defined - # @param dynamic_introspection [Boolean] (Private, used by GraphQL-Ruby) - # @param relay_node_field [Boolean] (Private, used by GraphQL-Ruby) - # @param relay_nodes_field [Boolean] (Private, used by GraphQL-Ruby) - # @param extras [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] Extra arguments to be injected into the resolver for this field - # @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}. + # **Parameters** + # + # - `name` (`Symbol`) — The underscore-cased version of this field name (will be camelized for the GraphQL API) + # - `type` (`Class, GraphQL::BaseType, Array`) — The return type of this field + # - `owner` (`Class`) — The type that this field belongs to + # - `null` (`Boolean`) — (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null` + # - `description` (`String`) — Field description + # - `comment` (`String`) — Field comment + # - `deprecation_reason` (`String`) — If present, the field is marked "deprecated" with this message + # - `method` (`Symbol`) — The method to call on the underlying object to resolve this field (defaults to `name`) + # - `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`) + # - `dig` (`Array`) — The nested hash keys to lookup on the underlying hash to resolve this field using dig + # - `resolver_method` (`Symbol`) — The method on the type to call to resolve this field (defaults to `name`) + # - `connection` (`Boolean`) — `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name + # - `connection_extension` (`Class`) — The extension to add, to implement connections. If `nil`, no extension is added. + # - `resolve_static` (`Symbol, true, nil`) — Used by `Schema.execute_next` to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments` + # - `resolve_batch` (`Symbol, true, nil`) — Used by `Schema.execute_next` map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`. + # - `resolve_each` (`Symbol, true, nil`) — Used by `Schema.execute_next` to get a value value for each item. Called on the owner type class with `object, context, **arguments`. + # - `resolve_legacy_instance_method` (`Symbol, true, nil`) — Used by `Schema.execute_next` to get a value value for each item. Calls an instance method on the object type class. + # - `dataload` (`Class, Hash`) — Shorthand for making dataloader calls + # - `max_page_size` (`Integer, nil`) — For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results. + # - `default_page_size` (`Integer, nil`) — For connections, the default number of items to return from this field, or `nil` to return unlimited results. + # - `introspection` (`Boolean`) — If true, this field will be marked as `#introspection?` and the name may begin with `__` + # - `resolver_class` (`Class`) — (Private) A [Schema::Resolver](rdoc-ref:Schema::Resolver) which this field was derived from. Use `resolver:` to create a field with a resolver. + # - `arguments` (`{String=>GraphQL::Schema::Argument, Hash}`) — Arguments for this field (may be added in the block, also) + # - `camelize` (`Boolean`) — If true, the field name will be camelized when building the schema + # - `complexity` (`Numeric`) — When provided, set the complexity for this field + # - `scope` (`Boolean`) — If true, the return type's `.scope_items` method will be called on the return value + # - `subscription_scope` (`Symbol, String`) — A key in `context` which will be used to scope subscription payloads + # - `extensions` (`Array Object>>`) — Named extensions to apply to this field (see also [extension](rdoc-ref:GraphQL::Schema::Field#extension)) + # - `directives` (`Hash{Class => Hash}`) — Directives to apply to this field + # - `trace` (`Boolean`) — If true, a [GraphQL::Tracing](rdoc-ref:GraphQL::Tracing) tracer will measure this scalar field + # - `broadcastable` (`Boolean`) — Whether or not this field can be distributed in subscription broadcasts + # - `ast_node` (`Language::Nodes::FieldDefinition, nil`) — If this schema was parsed from definition, this AST node defined the field + # - `method_conflict_warning` (`Boolean`) — If false, skip the warning if this field's method conflicts with a built-in method + # - `validates` (`Array`) — Configurations for validating this field + # - `fallback_value` (`Object`) — A fallback value if the method is not defined + # - `dynamic_introspection` (`Boolean`) — (Private, used by GraphQL-Ruby) + # - `relay_node_field` (`Boolean`) — (Private, used by GraphQL-Ruby) + # - `relay_nodes_field` (`Boolean`) — (Private, used by GraphQL-Ruby) + # - `extras` (`Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>`) — Extra arguments to be injected into the resolver for this field + # - `definition_block` (`Proc`) — an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new [Field](rdoc-ref:Field). + # + # :call-seq: + # initialize(Class | GraphQL::BaseType | Array type:, Symbol name:, Class owner:, bool null:, String description:, String comment:, String deprecation_reason:, Symbol method:, Symbol | true | nil resolve_legacy_instance_method:, Symbol | true | nil resolve_static:, Symbol | true | nil resolve_each:, Symbol | true | nil resolve_batch:, String | Symbol hash_key:, Array[String | Symbol] dig:, Symbol resolver_method:, bool connection:, Integer | nil max_page_size:, Integer | nil default_page_size:, bool scope:, bool introspection:, bool camelize:, bool trace:, Numeric complexity:, Class | Hash dataload:, Language::Nodes::FieldDefinition | nil ast_node:, Array[:ast_node | :parent | :lookahead | :owner | :execution_errors | :graphql_name | :argument_details | Symbol] extras:, Array[Class | Hash[Class, Object]] extensions:, Class connection_extension:, Class resolver_class:, Symbol | String subscription_scope:, bool relay_node_field:, bool relay_nodes_field:, bool method_conflict_warning:, bool broadcastable:, {String=>GraphQL::Schema::Argument | Hash} arguments:, Hash[Class, Hash] directives:, Array[Hash] validates:, Object fallback_value:, bool dynamic_introspection:, Proc &definition_block) def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, resolve_legacy_instance_method: nil, resolve_static: nil, resolve_each: nil, resolve_batch: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, dataload: nil, 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: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block) if name.nil? raise ArgumentError, "missing first `name` argument or keyword `name:`" @@ -368,15 +741,16 @@ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CON end end - # @api private - attr_reader :execution_mode_key, :execution_mode + attr_reader :execution_mode_key, :execution_mode # :nodoc: # Calls the definition block, if one was given. # This is deferred so that references to the return type # can be lazily evaluated, reducing Rails boot time. - # @return [self] - # @api private - def ensure_loaded + # + # **Returns** + # + # - `self` + def ensure_loaded # :nodoc: if @definition_block if @definition_block.arity == 1 @definition_block.call(self) @@ -393,8 +767,14 @@ def ensure_loaded attr_accessor :dynamic_introspection # If true, subscription updates with this field can be shared between viewers - # @return [Boolean, nil] - # @see GraphQL::Subscriptions::BroadcastAnalyzer + # See `GraphQL::Subscriptions::BroadcastAnalyzer`. + # + # **Returns** + # + # - `Boolean, nil` + # + # :call-seq: + # broadcastable?() -> bool | nil def broadcastable? if !NOT_CONFIGURED.equal?(@broadcastable) @broadcastable @@ -405,8 +785,16 @@ def broadcastable? end end - # @param text [String] - # @return [String] + # **Parameters** + # + # - `text` (`String`) + # + # **Returns** + # + # - `String` + # + # :call-seq: + # description(String text) -> String def description(text = nil) if text @description = text @@ -419,8 +807,16 @@ def description(text = nil) end end - # @param text [String] - # @return [String, nil] + # **Parameters** + # + # - `text` (`String`) + # + # **Returns** + # + # - `String, nil` + # + # :call-seq: + # comment(String text) -> String | nil def comment(text = nil) if text @comment = text @@ -437,17 +833,36 @@ def comment(text = nil) # 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]) + # **Examples** + # + # **Example: adding an extension** + # + # ```ruby + # extensions([MyExtensionClass]) + # ``` + # + # **Example: adding multiple extensions** + # + # ```ruby + # extensions([MyExtensionClass, AnotherExtensionClass]) + # ``` # - # @example adding multiple extensions - # extensions([MyExtensionClass, AnotherExtensionClass]) + # **Example: adding an extension with options** # - # @example adding an extension with options - # extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }]) + # ```ruby + # extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }]) + # ``` # - # @param extensions [Array Hash>>] Add extensions to this field. For hash elements, only the first key/value is used. - # @return [Array] extensions to apply to this field + # **Parameters** + # + # - `extensions` (`Array Hash>>`) — Add extensions to this field. For hash elements, only the first key/value is used. + # + # **Returns** + # + # - `Array` — extensions to apply to this field + # + # :call-seq: + # extensions(new_extensions) -> Array[GraphQL::Schema::FieldExtension] def extensions(new_extensions = nil) if new_extensions new_extensions.each do |extension_config| @@ -464,15 +879,31 @@ def extensions(new_extensions = nil) # Add `extension` to this field, initialized with `options` if provided. # - # @example adding an extension - # extension(MyExtensionClass) + # **Examples** + # + # **Example: adding an extension** + # + # ```ruby + # extension(MyExtensionClass) + # ``` # - # @example adding an extension with options - # extension(MyExtensionClass, filter: true) + # **Example: adding an extension with options** # - # @param extension_class [Class] subclass of {Schema::FieldExtension} - # @param options [Hash] if provided, given as `options:` when initializing `extension`. - # @return [void] + # ```ruby + # extension(MyExtensionClass, filter: true) + # ``` + # + # **Parameters** + # + # - `extension_class` (`Class`) — subclass of [Schema::FieldExtension](rdoc-ref:Schema::FieldExtension) + # - `options` (`Hash`) — if provided, given as `options:` when initializing `extension`. + # + # **Returns** + # + # - `void` + # + # :call-seq: + # extension(Class extension_class, Hash **options) -> void def extension(extension_class, **options) extension_inst = extension_class.new(field: self, options: options) if @extensions.frozen? @@ -488,8 +919,16 @@ def extension(extension_class, **options) # 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] + # **Parameters** + # + # - `new_extras` (`Array`) — Add extras to this field + # + # **Returns** + # + # - `Array` + # + # :call-seq: + # extras(Array[Symbol] new_extras) -> Array[Symbol] def extras(new_extras = nil) if new_extras.nil? # Read the value @@ -582,12 +1021,22 @@ def complexity(new_complexity = nil) end end - # @return [Boolean] True if this field's {#max_page_size} should override the schema default. + # **Returns** + # + # - `Boolean` — True if this field's [max page size](rdoc-ref:#max_page_size) should override the schema default. + # + # :call-seq: + # has_max_page_size?() -> bool def has_max_page_size? !NOT_CONFIGURED.equal?(@max_page_size) || (@resolver_class && @resolver_class.has_max_page_size?) end - # @return [Integer, nil] Applied to connections if {#has_max_page_size?} + # **Returns** + # + # - `Integer, nil` — Applied to connections if [has max page size?](rdoc-ref:#has_max_page_size?) + # + # :call-seq: + # max_page_size() -> Integer | nil def max_page_size if !NOT_CONFIGURED.equal?(@max_page_size) @max_page_size @@ -598,12 +1047,22 @@ def max_page_size end end - # @return [Boolean] True if this field's {#default_page_size} should override the schema default. + # **Returns** + # + # - `Boolean` — True if this field's [default page size](rdoc-ref:#default_page_size) should override the schema default. + # + # :call-seq: + # has_default_page_size?() -> bool def has_default_page_size? !NOT_CONFIGURED.equal?(@default_page_size) || (@resolver_class && @resolver_class.has_default_page_size?) end - # @return [Integer, nil] Applied to connections if {#has_default_page_size?} + # **Returns** + # + # - `Integer, nil` — Applied to connections if [has default page size?](rdoc-ref:#has_default_page_size?) + # + # :call-seq: + # default_page_size() -> Integer | nil def default_page_size if !NOT_CONFIGURED.equal?(@default_page_size) @default_page_size @@ -628,8 +1087,17 @@ class MissingReturnTypeError < GraphQL::Error; end # Get or set the return type of this field. # # It may return nil if no type was configured or if the given definition block wasn't called yet. - # @param new_type [Module, GraphQL::Schema::NonNull, GraphQL::Schema::List] A GraphQL return type - # @return [Module, GraphQL::Schema::NonNull, GraphQL::Schema::List, nil] the configured type for this field + # + # **Parameters** + # + # - `new_type` (`Module, GraphQL::Schema::NonNull, GraphQL::Schema::List`) — A GraphQL return type + # + # **Returns** + # + # - `Module, GraphQL::Schema::NonNull, GraphQL::Schema::List, nil` — the configured type for this field + # + # :call-seq: + # type(Module | GraphQL::Schema::NonNull | GraphQL::Schema::List new_type) -> Module | GraphQL::Schema::NonNull | GraphQL::Schema::List | nil def type(new_type = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_type) if @resolver_class @@ -716,9 +1184,15 @@ def authorized?(object, args, context) # 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] + # + # **Parameters** + # + # - `object` (`GraphQL::Schema::Object`) — An instance of some type class, wrapping an application object + # - `args` (`Hash`) — A symbol-keyed hash of Ruby keyword arguments. (Empty if no args) + # - `ctx` (`GraphQL::Query::Context`) + # + # :call-seq: + # resolve(GraphQL::Schema::Object object, Hash args, query_ctx) def resolve(object, args, query_ctx) # Unwrap the GraphQL object to get the application object. application_object = object.object @@ -824,7 +1298,12 @@ def resolve(object, args, query_ctx) err end - # @param ctx [GraphQL::Query::Context] + # **Parameters** + # + # - `ctx` (`GraphQL::Query::Context`) + # + # :call-seq: + # fetch_extra(extra_name, GraphQL::Query::Context ctx) def fetch_extra(extra_name, ctx) if extra_name != :path && extra_name != :ast_node && respond_to?(extra_name) self.public_send(extra_name) @@ -890,7 +1369,13 @@ def initialize(args, object) # Wrap execution with hooks. # Written iteratively to avoid big stack traces. - # @return [Object] Whatever the + # + # **Returns** + # + # - `Object` — Whatever the + # + # :call-seq: + # with_extensions(obj, args, ctx) -> Object def with_extensions(obj, args, ctx) if @extensions.empty? yield(obj, args) diff --git a/lib/graphql/schema/field_extension.rb b/lib/graphql/schema/field_extension.rb index b101f479626..ab377eb10a4 100644 --- a/lib/graphql/schema/field_extension.rb +++ b/lib/graphql/schema/field_extension.rb @@ -9,19 +9,40 @@ class Schema # 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] + # **Returns** + # + # - `GraphQL::Schema::Field` + # + # :call-seq: + # field -> GraphQL::Schema::Field attr_reader :field - # @return [Object] + # **Returns** + # + # - `Object` + # + # :call-seq: + # options -> Object attr_reader :options - # @return [Array, nil] `default_argument`s added, if any were added (otherwise, `nil`) + # **Returns** + # + # - `Array, nil` — `default_argument`s added, if any were added (otherwise, `nil`) + # + # :call-seq: + # added_default_arguments -> Array[Symbol] | nil attr_reader :added_default_arguments # Called when the extension is mounted with `extension(name, options)`. # The instance will be 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. + # + # **Parameters** + # + # - `field` (`GraphQL::Schema::Field`) — The field where this extension was mounted + # - `options` (`Object`) — The second argument to `extension`, or `{}` if nothing was passed. + # + # :call-seq: + # initialize(GraphQL::Schema::Field field:, Object options:) def initialize(field:, options:) @field = field @options = options || {} @@ -30,7 +51,12 @@ def initialize(field:, options:) end class << self - # @return [Array(Array, Hash), nil] A list of default argument configs, or `nil` if there aren't any + # **Returns** + # + # - `Array(Array, Hash), nil` — A list of default argument configs, or `nil` if there aren't any + # + # :call-seq: + # default_argument_configurations() -> Array(Array, Hash) | nil def default_argument_configurations args = superclass.respond_to?(:default_argument_configurations) ? superclass.default_argument_configurations : nil if @own_default_argument_configurations @@ -43,8 +69,8 @@ def default_argument_configurations args end - # @see Argument#initialize - # @see HasArguments#argument + # See the [GraphQL::Schema::Argument](rdoc-ref:GraphQL::Schema::Argument) API. + # See [HasArguments#argument](rdoc-ref:GraphQL::Schema::Member::HasArguments#argument) for argument configuration. def default_argument(*argument_args, **argument_kwargs) configs = @own_default_argument_configurations ||= [] configs << [argument_args, argument_kwargs] @@ -54,8 +80,16 @@ def default_argument(*argument_args, **argument_kwargs) # but removed by from `arguments` before the field's `resolve` is called. # (The extras _will_ be present for other extensions, though.) # - # @param new_extras [Array] If provided, assign extras used by this extension - # @return [Array] any extras assigned to this extension + # **Parameters** + # + # - `new_extras` (`Array`) — If provided, assign extras used by this extension + # + # **Returns** + # + # - `Array` — any extras assigned to this extension + # + # :call-seq: + # extras(Array[Symbol] new_extras) -> Array[Symbol] def extras(new_extras = nil) if new_extras @own_extras = new_extras @@ -78,18 +112,29 @@ def extras(new_extras = nil) # Called when this extension is attached to a field. # The field definition may be extended during this method. - # @return [void] + # + # **Returns** + # + # - `void` + # + # :call-seq: + # apply() -> void def apply end # Called after the field's definition block has been executed. # (Any arguments from the block are present on `field`) - # @return [void] + # + # **Returns** + # + # - `void` + # + # :call-seq: + # after_define() -> void def after_define end - # @api private - def after_define_apply + def after_define_apply # :nodoc: after_define if (configs = self.class.default_argument_configurations) existing_keywords = field.all_argument_definitions.map(&:keyword) @@ -113,41 +158,59 @@ def after_define_apply freeze end - # @api private - attr_reader :added_extras + attr_reader :added_extras # :nodoc: - # Called before resolving {#field}. It should either: + # Called before resolving [field](rdoc-ref:#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 (not passed by new execution) - # @param objects [Array] The objects the field is being resolved on (passed by new execution) - # @param arguments [Hash] Ruby keyword arguments for resolving this field - # @param context [Query::Context] the context for this query - # @yieldparam object_or_objects [Object, Array] The object or objects (new execution) 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. + # **Parameters** + # + # - `object` (`Object`) — The object the field is being resolved on (not passed by new execution) + # - `objects` (`Array`) — The objects the field is being resolved on (passed by new execution) + # - `arguments` (`Hash`) — Ruby keyword arguments for resolving this field + # - `context` (`Query::Context`) — the context for this query + # + # **Yields** + # + # - `object_or_objects` (`Object, Array`) — The object or objects (new execution) to continue resolving the field on + # - `arguments` (`Hash`) — The keyword arguments to continue resolving with + # - `memo` (`Object`) — Any extension-specific value which will be passed to [after resolve](rdoc-ref:#after_resolve) later + # + # **Returns** + # + # - `Object` — The return value for this field. + # + # :call-seq: + # resolve(Object object:, Array[Object] objects:, Hash arguments:, Query::Context context:) -> Object def resolve(object: nil, objects: nil, arguments:, context:) yield(object.nil? ? objects : object, arguments, nil) end - # Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced, + # Called after [field](rdoc-ref:#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 (not passed by new execution) - # @param objects [Array] The object the field is being resolved on (passed by new execution) - # @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 (not passed by new execution) - # @param values [Array] Whatever the field previously returned (passed by new execution) - # @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one - # @return [Object] The return value for this field. + # **Parameters** + # + # - `object` (`Object`) — The object the field is being resolved on (not passed by new execution) + # - `objects` (`Array`) — The object the field is being resolved on (passed by new execution) + # - `arguments` (`Hash`) — Ruby keyword arguments for resolving this field + # - `context` (`Query::Context`) — the context for this query + # - `value` (`Object`) — Whatever the field previously returned (not passed by new execution) + # - `values` (`Array`) — Whatever the field previously returned (passed by new execution) + # - `memo` (`Object`) — The third value yielded by [resolve](rdoc-ref:#resolve), or `nil` if there wasn't one + # + # **Returns** + # + # - `Object` — The return value for this field. + # + # :call-seq: + # after_resolve(Object object:, Array[Object] objects:, Hash arguments:, Query::Context context:, Array[Object] values:, Object value:, Object memo:) -> Object def after_resolve(object: nil, objects: nil, arguments:, context:, values: nil, value: nil, memo:) value.nil? ? values : value end diff --git a/lib/graphql/schema/finder.rb b/lib/graphql/schema/finder.rb index 6982986263e..5004f4a7667 100644 --- a/lib/graphql/schema/finder.rb +++ b/lib/graphql/schema/finder.rb @@ -4,18 +4,31 @@ module GraphQL class Schema # Find schema members using string paths # - # @example Finding object types - # MySchema.find("SomeObjectType") + # **Examples** # - # @example Finding fields - # MySchema.find("SomeObjectType.myField") + # **Example: Finding object types** # - # @example Finding arguments - # MySchema.find("SomeObjectType.myField.anArgument") + # ```ruby + # MySchema.find("SomeObjectType") + # ``` # - # @example Finding directives - # MySchema.find("@include") + # **Example: Finding fields** # + # ```ruby + # MySchema.find("SomeObjectType.myField") + # ``` + # + # **Example: Finding arguments** + # + # ```ruby + # MySchema.find("SomeObjectType.myField.anArgument") + # ``` + # + # **Example: Finding directives** + # + # ```ruby + # MySchema.find("@include") + # ``` class Finder class MemberNotFoundError < ArgumentError; end diff --git a/lib/graphql/schema/has_single_input_argument.rb b/lib/graphql/schema/has_single_input_argument.rb index 6dd4d3c421c..1e76961c879 100644 --- a/lib/graphql/schema/has_single_input_argument.rb +++ b/lib/graphql/schema/has_single_input_argument.rb @@ -111,8 +111,17 @@ def argument(*args, own_argument: false, **kwargs, &block) end # 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}) + # + # **Parameters** + # + # - `new_class` (`Class`) — The base class to use for generating input object definitions + # + # **Returns** + # + # - `Class` — The base class for this mutation's generated input object (default is [GraphQL::Schema::InputObject](rdoc-ref:GraphQL::Schema::InputObject)) + # + # :call-seq: + # input_object_class(Class new_class) -> Class def input_object_class(new_class = nil) if new_class @input_object_class = new_class @@ -120,8 +129,16 @@ def input_object_class(new_class = nil) @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` + # **Parameters** + # + # - `new_input_type` (`Class, nil`) — If provided, it configures this mutation to accept `new_input_type` instead of generating an input type + # + # **Returns** + # + # - `Class` — The generated [Schema::InputObject](rdoc-ref:Schema::InputObject) class for this mutation's `input` + # + # :call-seq: + # input_type(Class | nil new_input_type) -> Class def input_type(new_input_type = nil) if new_input_type @input_type = new_input_type @@ -133,7 +150,13 @@ def input_type(new_input_type = nil) # 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} + # + # **Returns** + # + # - `Class` — a subclass of [.input_object_class](rdoc-ref:.input_object_class) + # + # :call-seq: + # generate_input_type() -> Class def generate_input_type mutation_args = all_argument_definitions mutation_class = self diff --git a/lib/graphql/schema/input_object.rb b/lib/graphql/schema/input_object.rb index e5d49515c53..5eee64e779f 100644 --- a/lib/graphql/schema/input_object.rb +++ b/lib/graphql/schema/input_object.rb @@ -18,9 +18,19 @@ def initialize(input_object_type) end end - # @return [GraphQL::Query::Context] The context for this query + # **Returns** + # + # - `GraphQL::Query::Context` — The context for this query + # + # :call-seq: + # context -> GraphQL::Query::Context attr_reader :context - # @return [GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance + # **Returns** + # + # - `GraphQL::Execution::Interpereter::Arguments` — The underlying arguments instance + # + # :call-seq: + # arguments -> GraphQL::Execution::Interpereter::Arguments attr_reader :arguments # Ruby-like hash behaviors, read-only @@ -84,7 +94,13 @@ def unwrap_value(value) # Lookup a key on this object, it accepts new-style underscored symbols # Or old-style camelized identifiers. - # @param key [Symbol, String] + # + # **Parameters** + # + # - `key` (`Symbol, String`) + # + # :call-seq: + # [](Symbol | String key) def [](key) if @ruby_style_hash.key?(key) @ruby_style_hash[key] @@ -104,8 +120,7 @@ def to_kwargs @ruby_style_hash.dup end - # @api private - def validate_for(context) + def validate_for(context) # :nodoc: object = context[:current_object] # Pass this object's class with `as` so that messages are rendered correctly from inherited validators Schema::Validator.validate!(self.class.validators, object, context, @ruby_style_hash, as: self.class) @@ -165,7 +180,7 @@ def kind GraphQL::TypeKinds::INPUT_OBJECT end - # @api private + # :nodoc: INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object." def validate_non_null_input(input, ctx, max_errors: nil) @@ -270,14 +285,27 @@ def coerce_result(value, ctx) result end - # @param new_has_no_arguments [Boolean] Call with `true` to make this InputObject type ignore the requirement to have any defined arguments. - # @return [void] + # **Parameters** + # + # - `new_has_no_arguments` (`Boolean`) — Call with `true` to make this InputObject type ignore the requirement to have any defined arguments. + # + # **Returns** + # + # - `void` + # + # :call-seq: + # has_no_arguments(bool new_has_no_arguments) -> void def has_no_arguments(new_has_no_arguments) @has_no_arguments = new_has_no_arguments nil end - # @return [Boolean] `true` if `has_no_arguments(true)` was configued + # **Returns** + # + # - `Boolean` — `true` if `has_no_arguments(true)` was configued + # + # :call-seq: + # has_no_arguments?() -> bool def has_no_arguments? @has_no_arguments end diff --git a/lib/graphql/schema/interface.rb b/lib/graphql/schema/interface.rb index 80b4573cc7b..977a7c86551 100644 --- a/lib/graphql/schema/interface.rb +++ b/lib/graphql/schema/interface.rb @@ -35,14 +35,18 @@ def definition_methods(&block) # Instance methods defined in this block will become class methods on objects that implement this interface. # Use it to implement `resolve_each:`, `resolve_batch:`, and `resolve_static:` fields. - # @example - # field :thing, String, resolve_static: true # - # resolver_methods do - # def thing - # Somehow.get.thing - # end + # **Examples** + # + # **Example: field :thing, String, resolve_static: true** + # + # ```ruby + # resolver_methods do + # def thing + # Somehow.get.thing # end + # end + # ``` def resolver_methods(&block) if !defined?(@_resolver_methods) resolver_methods_module = Module.new @@ -53,7 +57,7 @@ def resolver_methods(&block) self::ResolverMethods.module_exec(&block) end - # @see {Schema::Warden} hides interfaces without visible implementations + # See [Schema::Warden](rdoc-ref:Schema::Warden) hides interfaces without visible implementations def visible?(context) true end @@ -114,8 +118,17 @@ def included(child_class) # # When those Interfaces or Objects aren't used as the return values of fields, # they may have to be registered using this method so that GraphQL-Ruby can find them. - # @param types [Class, Module] - # @return [Array] Implementers of this interface, if they're registered + # + # **Parameters** + # + # - `types` (`Class, Module`) + # + # **Returns** + # + # - `Array` — Implementers of this interface, if they're registered + # + # :call-seq: + # orphan_types(Class | Module *types) -> Array[Module | Class] def orphan_types(*types) if !types.empty? @orphan_types ||= [] diff --git a/lib/graphql/schema/introspection_system.rb b/lib/graphql/schema/introspection_system.rb index 8051bda2a1c..a7fb4617843 100644 --- a/lib/graphql/schema/introspection_system.rb +++ b/lib/graphql/schema/introspection_system.rb @@ -64,9 +64,11 @@ def dynamic_field(name:) # Replace those with the objects that they refer to, since LateBoundTypes # aren't handled at runtime. # - # @api private - # @return void - def resolve_late_bindings + # + # **Returns** + # + # - `Object` — void + def resolve_late_bindings # :nodoc: @types.each do |name, t| if t.kind.fields? t.all_field_definitions.each do |field_defn| diff --git a/lib/graphql/schema/late_bound_type.rb b/lib/graphql/schema/late_bound_type.rb index e8ec59660af..e68acad80fe 100644 --- a/lib/graphql/schema/late_bound_type.rb +++ b/lib/graphql/schema/late_bound_type.rb @@ -3,8 +3,7 @@ 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 + class LateBoundType # :nodoc: attr_reader :name alias :graphql_name :name def initialize(local_name) diff --git a/lib/graphql/schema/list.rb b/lib/graphql/schema/list.rb index 56e0928f58f..3358fc95981 100644 --- a/lib/graphql/schema/list.rb +++ b/lib/graphql/schema/list.rb @@ -3,17 +3,27 @@ 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 Create a list type from another GraphQL type + # Wraps a [Schema::Member](rdoc-ref:Schema::Member) as a list type. + # See [Schema::Member::TypeSystemHelpers#to_list_type](rdoc-ref:Schema::Member::TypeSystemHelpers#to_list_type) Create a list type from another GraphQL type class List < GraphQL::Schema::Wrapper include Schema::Member::ValidatesInput - # @return [GraphQL::TypeKinds::LIST] + # **Returns** + # + # - `GraphQL::TypeKinds::LIST` + # + # :call-seq: + # kind() -> GraphQL::TypeKinds::LIST def kind GraphQL::TypeKinds::LIST end - # @return [true] + # **Returns** + # + # - `true` + # + # :call-seq: + # list?() -> true def list? true end diff --git a/lib/graphql/schema/loader.rb b/lib/graphql/schema/loader.rb index 228cb5161b0..89284ec33d4 100644 --- a/lib/graphql/schema/loader.rb +++ b/lib/graphql/schema/loader.rb @@ -1,18 +1,27 @@ # frozen_string_literal: true module GraphQL class Schema - # You can use the result of {GraphQL::Introspection::INTROSPECTION_QUERY} + # You can use the result of [GraphQL::Introspection::INTROSPECTION_QUERY](rdoc-ref: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 + # See [GraphQL::Schema.from_introspection](rdoc-ref: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` + # + # **Parameters** + # + # - `introspection_result` (`Hash`) — A response from [GraphQL::Introspection::INTROSPECTION_QUERY](rdoc-ref:GraphQL::Introspection::INTROSPECTION_QUERY) + # + # **Returns** + # + # - `Class` — the schema described by `input` + # + # :call-seq: + # load(Hash introspection_result) -> Class def load(introspection_result) schema = introspection_result.fetch("data").fetch("__schema") diff --git a/lib/graphql/schema/member.rb b/lib/graphql/schema/member.rb index 11c15cb858e..1b70354f9e9 100644 --- a/lib/graphql/schema/member.rb +++ b/lib/graphql/schema/member.rb @@ -20,8 +20,7 @@ class Schema # The base class for things that make up the schema, # eg objects, enums, scalars. # - # @api private - class Member + class Member # :nodoc: include GraphQLTypeNames extend BaseDSLMethods extend BaseDSLMethods::ConfigurationExtension diff --git a/lib/graphql/schema/member/base_dsl_methods.rb b/lib/graphql/schema/member/base_dsl_methods.rb index ce91290004e..20b977022d4 100644 --- a/lib/graphql/schema/member/base_dsl_methods.rb +++ b/lib/graphql/schema/member/base_dsl_methods.rb @@ -6,17 +6,25 @@ 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 + # See classes that extend this, eg [GraphQL::Schema::Object](rdoc-ref:GraphQL::Schema::Object) + module BaseDSLMethods # :nodoc: 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] + # + # **Parameters** + # + # - `new_name` (`String`) + # + # **Returns** + # + # - `String` + # + # :call-seq: + # graphql_name(String new_name) -> String def graphql_name(new_name = nil) if new_name GraphQL::NameValidator.validate!(new_name) @@ -28,8 +36,17 @@ def graphql_name(new_name = nil) # Call this method to provide a new description; OR # call it without an argument to get the description - # @param new_description [String] - # @return [String] + # + # **Parameters** + # + # - `new_description` (`String`) + # + # **Returns** + # + # - `String` + # + # :call-seq: + # description(String new_description) -> String def description(new_description = nil) if new_description @description = new_description @@ -42,8 +59,17 @@ def description(new_description = nil) # Call this method to provide a new comment; OR # call it without an argument to get the comment - # @param new_comment [String] - # @return [String, nil] + # + # **Parameters** + # + # - `new_comment` (`String`) + # + # **Returns** + # + # - `String, nil` + # + # :call-seq: + # comment(String new_comment) -> String | nil def comment(new_comment = NOT_CONFIGURED) if !NOT_CONFIGURED.equal?(new_comment) @comment = new_comment @@ -72,7 +98,12 @@ def inherited(child_class) end end - # @return [Boolean] If true, this object is part of the introspection system + # **Returns** + # + # - `Boolean` — If true, this object is part of the introspection system + # + # :call-seq: + # introspection(new_introspection) -> bool def introspection(new_introspection = nil) if !new_introspection.nil? @introspection = new_introspection @@ -88,7 +119,13 @@ def introspection? end # The mutation this type was derived from, if it was derived from a mutation - # @return [Class] + # + # **Returns** + # + # - `Class` + # + # :call-seq: + # mutation(mutation_class) -> Class def mutation(mutation_class = nil) if mutation_class @mutation = mutation_class diff --git a/lib/graphql/schema/member/build_type.rb b/lib/graphql/schema/member/build_type.rb index 3f8dcec16b4..cbcf54b3912 100644 --- a/lib/graphql/schema/member/build_type.rb +++ b/lib/graphql/schema/member/build_type.rb @@ -2,13 +2,20 @@ module GraphQL class Schema class Member - # @api private - module BuildType + module BuildType # :nodoc: 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] + # **Parameters** + # + # - `type_expr` (`String, Class, GraphQL::BaseType`) + # + # **Returns** + # + # - `GraphQL::BaseType` + # + # :call-seq: + # parse_type(String | Class | GraphQL::BaseType type_expr, null:) -> GraphQL::BaseType def parse_type(type_expr, null:) list_type = false diff --git a/lib/graphql/schema/member/graphql_type_names.rb b/lib/graphql/schema/member/graphql_type_names.rb index 85f81a99987..dda97530118 100644 --- a/lib/graphql/schema/member/graphql_type_names.rb +++ b/lib/graphql/schema/member/graphql_type_names.rb @@ -5,13 +5,16 @@ 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 + # **Examples** + # + # **Example: field :is_draft, Boolean, null: false** + # + # ```ruby + # field :id, ID, null: false + # field :score, Int, null: false + # ``` + module GraphQLTypeNames # :nodoc: Boolean = "Boolean" ID = "ID" Int = "Int" diff --git a/lib/graphql/schema/member/has_arguments.rb b/lib/graphql/schema/member/has_arguments.rb index 98b36a8de6e..009151737e3 100644 --- a/lib/graphql/schema/member/has_arguments.rb +++ b/lib/graphql/schema/member/has_arguments.rb @@ -14,28 +14,39 @@ def self.extended(cls) cls.extend(ClassConfigured) end - # @param arg_name [Symbol] The underscore-cased name of this argument, `name:` keyword also accepted - # @param type_expr The GraphQL type of this argument; `type:` keyword also accepted - # @param desc [String] Argument description, `description:` keyword also accepted - # @option kwargs [Boolean, :nullable] :required if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`. - # @option kwargs [String] :description Positional argument also accepted - # @option kwargs [Class, Array] :type Input type; positional argument also accepted - # @option kwargs [Symbol] :name positional argument also accepted - # @option kwargs [Object] :default_value - # @option kwargs [Class, Array] :loads A GraphQL type to load for the given ID when one is present - # @option kwargs [Symbol] :as Override the keyword name when passed to a method - # @option kwargs [Symbol] :prepare A method to call to transform this argument's valuebefore sending it to field resolution - # @option kwargs [Boolean] :camelize if true, the name will be camelized when building the schema - # @option kwargs [Boolean] :from_resolver if true, a Resolver class defined this argument - # @option kwargs [Hash{Class => Hash}] :directives - # @option kwargs [String] :deprecation_reason - # @option kwargs [String] :comment Private, used by GraphQL-Ruby when parsing GraphQL schema files - # @option kwargs [GraphQL::Language::Nodes::InputValueDefinition] :ast_node Private, used by GraphQL-Ruby when parsing schema files - # @option kwargs [Hash, nil] :validates Options for building validators, if any should be applied - # @option kwargs [Boolean] :replace_null_with_default if `true`, incoming values of `null` will be replaced with the configured `default_value` - # @param definition_block [Proc] Called with the newly-created {Argument} - # @param kwargs [Hash] Keywords for defining an argument. Any keywords not documented here must be handled by your base Argument class. - # @return [GraphQL::Schema::Argument] An instance of {argument_class} created from these arguments + # **Parameters** + # + # - `arg_name` (`Symbol`) — The underscore-cased name of this argument, `name:` keyword also accepted + # - `type_expr` — The GraphQL type of this argument; `type:` keyword also accepted + # - `desc` (`String`) — Argument description, `description:` keyword also accepted + # - `definition_block` (`Proc`) — Called with the newly-created [Argument](rdoc-ref:Argument) + # - `kwargs` (`Hash`) — Keywords for defining an argument. Any keywords not documented here must be handled by your base Argument class. + # + # **Options** + # + # - `kwargs.:required` (`Boolean, :nullable`) — if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`. + # - `kwargs.:description` (`String`) — Positional argument also accepted + # - `kwargs.:type` (`Class, Array`) — Input type; positional argument also accepted + # - `kwargs.:name` (`Symbol`) — positional argument also accepted + # - `kwargs.:default_value` (`Object`) + # - `kwargs.:loads` (`Class, Array`) — A GraphQL type to load for the given ID when one is present + # - `kwargs.:as` (`Symbol`) — Override the keyword name when passed to a method + # - `kwargs.:prepare` (`Symbol`) — A method to call to transform this argument's valuebefore sending it to field resolution + # - `kwargs.:camelize` (`Boolean`) — if true, the name will be camelized when building the schema + # - `kwargs.:from_resolver` (`Boolean`) — if true, a Resolver class defined this argument + # - `kwargs.:directives` (`Hash{Class => Hash}`) + # - `kwargs.:deprecation_reason` (`String`) + # - `kwargs.:comment` (`String`) — Private, used by GraphQL-Ruby when parsing GraphQL schema files + # - `kwargs.:ast_node` (`GraphQL::Language::Nodes::InputValueDefinition`) — Private, used by GraphQL-Ruby when parsing schema files + # - `kwargs.:validates` (`Hash, nil`) — Options for building validators, if any should be applied + # - `kwargs.:replace_null_with_default` (`Boolean`) — if `true`, incoming values of `null` will be replaced with the configured `default_value` + # + # **Returns** + # + # - `GraphQL::Schema::Argument` — An instance of [argument_class](rdoc-ref:argument_class) created from these arguments + # + # :call-seq: + # argument(Symbol arg_name, type_expr, String desc, Hash **kwargs, Proc &definition_block) -> GraphQL::Schema::Argument def argument(arg_name = nil, type_expr = nil, desc = nil, **kwargs, &definition_block) if kwargs[:loads] loads_name = arg_name || kwargs[:name] @@ -65,8 +76,17 @@ def argument(arg_name = nil, type_expr = nil, desc = nil, **kwargs, &definition_ end # Register this argument with the class. - # @param arg_defn [GraphQL::Schema::Argument] - # @return [GraphQL::Schema::Argument] + # + # **Parameters** + # + # - `arg_defn` (`GraphQL::Schema::Argument`) + # + # **Returns** + # + # - `GraphQL::Schema::Argument` + # + # :call-seq: + # add_argument(GraphQL::Schema::Argument arg_defn) -> GraphQL::Schema::Argument def add_argument(arg_defn) @own_arguments ||= {} prev_defn = @own_arguments[arg_defn.name] @@ -98,7 +118,12 @@ def remove_argument(arg_defn) nil end - # @return [Hash GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions + # **Returns** + # + # - `Hash GraphQL::Schema::Argument>` — Arguments defined on this thing, keyed by name. Includes inherited definitions + # + # :call-seq: + # arguments(context:, _require_defined_arguments) -> Hash[String, GraphQL::Schema::Argument] def arguments(context = GraphQL::Query::NullContext.instance, _require_defined_arguments = nil) if !own_arguments.empty? own_arguments_that_apply = {} @@ -230,7 +255,12 @@ def all_argument_definitions end end - # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name. + # **Returns** + # + # - `GraphQL::Schema::Argument, nil` — Argument defined on this thing, fetched by name. + # + # :call-seq: + # get_argument(argument_name, context:) -> GraphQL::Schema::Argument | nil def get_argument(argument_name, context = GraphQL::Query::NullContext.instance) warden = Warden.from_context(context) if (arg_config = own_arguments[argument_name]) && ((context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)) || (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))) @@ -242,21 +272,27 @@ def get_argument(argument_name, context = GraphQL::Query::NullContext.instance) end end - # @param new_arg_class [Class] A class to use for building argument definitions + # **Parameters** + # + # - `new_arg_class` (`Class`) — A class to use for building argument definitions + # + # :call-seq: + # argument_class(Class new_arg_class) def argument_class(new_arg_class = nil) self.class.argument_class(new_arg_class) end - # @api private - # If given a block, it will eventually yield the loaded args to the block. + # **Yields:** [Interpreter::Arguments, Execution::Lazy] # - # If no block is given, it will immediately dataload (but might return a Lazy). + # **Parameters** # - # @param values [Hash] - # @param context [GraphQL::Query::Context] - # @yield [Interpreter::Arguments, Execution::Lazy] - # @return [Interpreter::Arguments, Execution::Lazy] - def coerce_arguments(parent_object, values, context, &block) + # - `values` (`Hash`) + # - `context` (`GraphQL::Query::Context`) + # + # **Returns** + # + # - `Interpreter::Arguments, Execution::Lazy` + def coerce_arguments(parent_object, values, context, &block) # :nodoc: # Cache this hash to avoid re-merging it arg_defns = context.types.arguments(self) total_args_count = arg_defns.size @@ -348,12 +384,17 @@ def argument_class(new_arg_class = nil) module ArgumentObjectLoader # Look up the corresponding object for a provided ID. - # By default, it uses Relay-style {Schema.object_from_id}, + # By default, it uses Relay-style [Schema.object_from_id](rdoc-ref: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 + # **Parameters** + # + # - `type` (`Class, Module`) — A GraphQL type definition + # - `id` (`String`) — A client-provided to look up + # - `context` (`GraphQL::Query::Context`) — the current context + # + # :call-seq: + # object_from_id(Class | Module type, String id, GraphQL::Query::Context context) def object_from_id(type, id, context) context.schema.object_from_id(id, context) end @@ -439,9 +480,18 @@ def authorize_application_object(argument, id, context, loaded_application_objec # Called when an argument's `loads:` configuration fails to fetch an application object. # By default, this method raises the given error, but you can override it to handle failures differently. # - # @param err [GraphQL::LoadApplicationObjectFailedError] The error that occurred - # @return [Object, nil] If a value is returned, it will be used instead of the failed load - # @api public + # **API:** public + # + # **Parameters** + # + # - `err` (`GraphQL::LoadApplicationObjectFailedError`) — The error that occurred + # + # **Returns** + # + # - `Object, nil` — If a value is returned, it will be used instead of the failed load + # + # :call-seq: + # load_application_object_failed(GraphQL::LoadApplicationObjectFailedError err) -> Object | nil def load_application_object_failed(err) raise err end diff --git a/lib/graphql/schema/member/has_dataloader.rb b/lib/graphql/schema/member/has_dataloader.rb index fb50b18273b..0a11762364a 100644 --- a/lib/graphql/schema/member/has_dataloader.rb +++ b/lib/graphql/schema/member/has_dataloader.rb @@ -3,19 +3,29 @@ module GraphQL class Schema class Member - # @api public - # Shared methods for working with {Dataloader} inside GraphQL runtime objects. + # **API:** public Shared methods for working with [Dataloader](rdoc-ref:Dataloader) inside GraphQL runtime objects. module HasDataloader - # @return [GraphQL::Dataloader] The dataloader for the currently-running query + # **Returns** + # + # - `GraphQL::Dataloader` — The dataloader for the currently-running query + # + # :call-seq: + # dataloader() -> GraphQL::Dataloader def dataloader context.dataloader end # A shortcut method for loading a key from a source. # Identical to `dataloader.with(source_class, *source_args).load(load_key)` - # @param source_class [Class] - # @param source_args [Array] Any extra parameters defined in `source_class`'s `initialize` method - # @param load_key [Object] The key to look up using `def fetch` + # + # **Parameters** + # + # - `source_class` (`Class`) + # - `source_args` (`Array`) — Any extra parameters defined in `source_class`'s `initialize` method + # - `load_key` (`Object`) — The key to look up using `def fetch` + # + # :call-seq: + # dataload(Class[GraphQL::Dataloader::Source] source_class, Array[Object] *source_args, Object load_key) def dataload(source_class, *source_args, load_key) dataloader.with(source_class, *source_args).load(load_key) end @@ -23,29 +33,56 @@ def dataload(source_class, *source_args, load_key) # A shortcut method for loading many keys from a source. # Identical to `dataloader.with(source_class, *source_args).load_all(load_keys)` # - # @example - # field :score, Integer, resolve_batch: true + # **Examples** + # + # **Example: field :score, Integer, resolve_batch: true** + # + # ```ruby + # def self.score(posts) + # dataload_all(PostScoreSource, posts.map(&:id)) + # end + # ``` # - # def self.score(posts) - # dataload_all(PostScoreSource, posts.map(&:id)) - # end + # **Parameters** # - # @param source_class [Class] - # @param source_args [Array] Any extra parameters defined in `source_class`'s `initialize` method - # @param load_keys [Array] The keys to look up using `def fetch` + # - `source_class` (`Class`) + # - `source_args` (`Array`) — Any extra parameters defined in `source_class`'s `initialize` method + # - `load_keys` (`Array`) — The keys to look up using `def fetch` + # + # :call-seq: + # dataload_all(Class[GraphQL::Dataloader::Source] source_class, Array[Object] *source_args, Array[Object] load_keys) def dataload_all(source_class, *source_args, load_keys) dataloader.with(source_class, *source_args).load_all(load_keys) end - # Find an object with ActiveRecord via {Dataloader::ActiveRecordSource}. - # @param model [Class] - # @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided - # @param find_by [Symbol, String] A column name to look the record up by. (Defaults to the model's primary key.) - # @return [ActiveRecord::Base, nil] - # @example Finding a record by ID - # dataload_record(Post, 5) # Like `Post.find(5)`, but dataloaded - # @example Finding a record by another attribute - # dataload_record(User, "matz", find_by: :handle) # Like `User.find_by(handle: "matz")`, but dataloaded + # Find an object with ActiveRecord via [Dataloader::ActiveRecordSource](rdoc-ref:Dataloader::ActiveRecordSource). + # + # **Parameters** + # + # - `model` (`Class`) + # - `find_by_value` (`Object`) — Usually an `id`, might be another value if `find_by:` is also provided + # - `find_by` (`Symbol, String`) — A column name to look the record up by. (Defaults to the model's primary key.) + # + # **Returns** + # + # - `ActiveRecord::Base, nil` + # + # **Examples** + # + # **Example: Finding a record by ID** + # + # ```ruby + # dataload_record(Post, 5) # Like `Post.find(5)`, but dataloaded + # ``` + # + # **Example: Finding a record by another attribute** + # + # ```ruby + # dataload_record(User, "matz", find_by: :handle) # Like `User.find_by(handle: "matz")`, but dataloaded + # ``` + # + # :call-seq: + # dataload_record(Class[ActiveRecord::Base] model, Object find_by_value, Symbol | String find_by:) -> ActiveRecord::Base | nil def dataload_record(model, find_by_value, find_by: nil) source = if find_by dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by) @@ -56,7 +93,7 @@ def dataload_record(model, find_by_value, find_by: nil) source.load(find_by_value) end - # @see dataload_record Like `dataload_record`, but accepts an Array of `find_by_values` + # See [dataload_record](rdoc-ref:dataload_record) Like `dataload_record`, but accepts an Array of `find_by_values` def dataload_all_records(model, find_by_values, find_by: nil) source = if find_by dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by) @@ -66,15 +103,34 @@ def dataload_all_records(model, find_by_values, find_by: nil) source.load_all(find_by_values) end - # Look up an associated record using a Rails association (via {Dataloader::ActiveRecordAssociationSource}) - # @param association_name [Symbol] A `belongs_to` or `has_one` association. (If a `has_many` association is named here, it will be selected without pagination.) - # @param record [ActiveRecord::Base] The object that the association belongs to. - # @param scope [ActiveRecord::Relation] A scope to look up the associated record in - # @return [ActiveRecord::Base, nil] The associated record, if there is one - # @example Looking up a belongs_to on the current object - # dataload_association(:parent) # Equivalent to `object.parent`, but dataloaded - # @example Looking up an associated record on some other object - # dataload_association(comment, :post) # Equivalent to `comment.post`, but dataloaded + # Look up an associated record using a Rails association (via [Dataloader::ActiveRecordAssociationSource](rdoc-ref:Dataloader::ActiveRecordAssociationSource)) + # + # **Parameters** + # + # - `association_name` (`Symbol`) — A `belongs_to` or `has_one` association. (If a `has_many` association is named here, it will be selected without pagination.) + # - `record` (`ActiveRecord::Base`) — The object that the association belongs to. + # - `scope` (`ActiveRecord::Relation`) — A scope to look up the associated record in + # + # **Returns** + # + # - `ActiveRecord::Base, nil` — The associated record, if there is one + # + # **Examples** + # + # **Example: Looking up a belongs_to on the current object** + # + # ```ruby + # dataload_association(:parent) # Equivalent to `object.parent`, but dataloaded + # ``` + # + # **Example: Looking up an associated record on some other object** + # + # ```ruby + # dataload_association(comment, :post) # Equivalent to `comment.post`, but dataloaded + # ``` + # + # :call-seq: + # dataload_association(ActiveRecord::Base record, Symbol association_name, ActiveRecord::Relation scope:) -> ActiveRecord::Base | nil def dataload_association(record = object, association_name, scope: nil) source = if scope dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope) @@ -84,7 +140,7 @@ def dataload_association(record = object, association_name, scope: nil) source.load(record) end - # @see dataload_association Like `dataload_assocation` but accepts an Array of records (required param) + # See [dataload_association](rdoc-ref:dataload_association) Like `dataload_assocation` but accepts an Array of records (required param) def dataload_all_associations(records, association_name, scope: nil) source = if scope dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope) diff --git a/lib/graphql/schema/member/has_deprecation_reason.rb b/lib/graphql/schema/member/has_deprecation_reason.rb index 8c98f61ab92..6ee6315d8a1 100644 --- a/lib/graphql/schema/member/has_deprecation_reason.rb +++ b/lib/graphql/schema/member/has_deprecation_reason.rb @@ -4,11 +4,22 @@ module GraphQL class Schema class Member module HasDeprecationReason - # @return [String, nil] Explains why this member was deprecated (if present, this will be marked deprecated in introspection) + # **Returns** + # + # - `String, nil` — Explains why this member was deprecated (if present, this will be marked deprecated in introspection) + # + # :call-seq: + # deprecation_reason -> String | nil attr_reader :deprecation_reason # Set the deprecation reason for this member, or remove it by assigning `nil` - # @param text [String, nil] + # + # **Parameters** + # + # - `text` (`String, nil`) + # + # :call-seq: + # deprecation_reason=(String | nil text) def deprecation_reason=(text) @deprecation_reason = text if text.nil? diff --git a/lib/graphql/schema/member/has_directives.rb b/lib/graphql/schema/member/has_directives.rb index 651c5cdfb7f..4c2fa979df2 100644 --- a/lib/graphql/schema/member/has_directives.rb +++ b/lib/graphql/schema/member/has_directives.rb @@ -18,7 +18,12 @@ def inherited(child_cls) # # It removes a previously-attached instance of `dir_class`, if there is one. # - # @return [void] + # **Returns** + # + # - `void` + # + # :call-seq: + # directive(dir_class, **options) -> void def directive(dir_class, **options) @own_directives ||= [] HasDirectives.add_directive(self, @own_directives, dir_class, options) @@ -26,8 +31,17 @@ def directive(dir_class, **options) end # Remove an attached instance of `dir_class`, if there is one - # @param dir_class [Class] - # @return [viod] + # + # **Parameters** + # + # - `dir_class` (`Class`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # remove_directive(Class[GraphQL::Schema::Directive] dir_class) -> void def remove_directive(dir_class) HasDirectives.remove_directive(@own_directives, dir_class) nil @@ -93,9 +107,18 @@ def get_directives(schema_member, directives, directives_method) # Modify `target` by adding items from `dirs` such that: # - Any name conflict is overridden by the incoming member of `dirs` # - Any other member of `dirs` is appended - # @param target [Array] - # @param dirs [Array] - # @return [void] + # + # **Parameters** + # + # - `target` (`Array`) + # - `dirs` (`Array`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # merge_directives(Array[GraphQL::Schema::Directive] target, Array[GraphQL::Schema::Directive] dirs) -> void def merge_directives(target, dirs) dirs.each do |dir| if (idx = target.find_index { |d| d.graphql_name == dir.graphql_name }) diff --git a/lib/graphql/schema/member/has_fields.rb b/lib/graphql/schema/member/has_fields.rb index 6e328a70e60..34cd8e415ea 100644 --- a/lib/graphql/schema/member/has_fields.rb +++ b/lib/graphql/schema/member/has_fields.rb @@ -7,54 +7,69 @@ class Member module HasFields include EmptyObjects # Add a field to this object or interface with the given definition - # @param name_positional [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API); `name:` keyword is also accepted - # @param type_positional [Class, GraphQL::BaseType, Array] The return type of this field; `type:` keyword is also accepted - # @param desc_positional [String] Field description; `description:` keyword is also accepted - # @option kwargs [Symbol] :name The underscore-cased version of this field name (will be camelized for the GraphQL API); positional argument also accepted - # @option kwargs [Class, GraphQL::BaseType, Array] :type The return type of this field; positional argument is also accepted - # @option kwargs [Boolean] :null (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null` - # @option kwargs [String] :description Field description; positional argument also accepted - # @option kwargs [String] :comment Field comment - # @option kwargs [String] :deprecation_reason If present, the field is marked "deprecated" with this message - # @option kwargs [Symbol] :method The method to call on the underlying object to resolve this field (defaults to `name`) - # @option kwargs [String, Symbol] :hash_key The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`) - # @option kwargs [Array] :dig The nested hash keys to lookup on the underlying hash to resolve this field using dig - # @option kwargs [Symbol, true] :resolver_method The method on the type to call to resolve this field (defaults to `name`) - # @option kwargs [Symbol, true] :resolve_static Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments` - # @option kwargs [Symbol, true] :resolve_batch Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`. - # @option kwargs [Symbol, true] :resolve_each Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`. - # @option kwargs [Symbol, true] :resolve_legacy_instance_method Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class. - # @option kwargs [Boolean] :connection `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name - # @option kwargs [Class] :connection_extension The extension to add, to implement connections. If `nil`, no extension is added. - # @option kwargs [Integer, nil] :max_page_size For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results. - # @option kwargs [Integer, nil] :default_page_size For connections, the default number of items to return from this field, or `nil` to return unlimited results. - # @option kwargs [Boolean] :introspection If true, this field will be marked as `#introspection?` and the name may begin with `__` - # @option kwargs [{String=>GraphQL::Schema::Argument, Hash}] :arguments Arguments for this field (may be added in the block, also) - # @option kwargs [Boolean] :camelize If true, the field name will be camelized when building the schema - # @option kwargs [Numeric] :complexity When provided, set the complexity for this field - # @option kwargs [Boolean] :scope If true, the return type's `.scope_items` method will be called on the return value - # @option kwargs [Symbol, String] :subscription_scope A key in `context` which will be used to scope subscription payloads - # @option kwargs [Array Object>>] :extensions Named extensions to apply to this field (see also {#extension}) - # @option kwargs [Hash{Class => Hash}] :directives Directives to apply to this field - # @option kwargs [Boolean] :trace If true, a {GraphQL::Tracing} tracer will measure this scalar field - # @option kwargs [Boolean] :broadcastable Whether or not this field can be distributed in subscription broadcasts - # @option kwargs [Language::Nodes::FieldDefinition, nil] :ast_node If this schema was parsed from definition, this AST node defined the field - # @option kwargs [Boolean] :method_conflict_warning If false, skip the warning if this field's method conflicts with a built-in method - # @option kwargs [Array] :validates Configurations for validating this field - # @option kwargs [Object] :fallback_value A fallback value if the method is not defined - # @option kwargs [Class] :mutation - # @option kwargs [Class] :resolver - # @option kwargs [Class] :subscription - # @option kwargs [Boolean] :dynamic_introspection (Private, used by GraphQL-Ruby) - # @option kwargs [Boolean] :relay_node_field (Private, used by GraphQL-Ruby) - # @option kwargs [Boolean] :relay_nodes_field (Private, used by GraphQL-Ruby) - # @option kwargs [Class, Hash] :dataload Shorthand for dataloader lookups - # @option kwargs [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] :extras Extra arguments to be injected into the resolver for this field - # @param kwargs [Hash] Keywords for defining the field. Any not documented here will be passed to your base field class where they must be handled. - # @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}. - # @yieldparam field [GraphQL::Schema::Field] The newly-created field instance - # @yieldreturn [void] - # @return [GraphQL::Schema::Field] + # + # **Parameters** + # + # - `name_positional` (`Symbol`) — The underscore-cased version of this field name (will be camelized for the GraphQL API); `name:` keyword is also accepted + # - `type_positional` (`Class, GraphQL::BaseType, Array`) — The return type of this field; `type:` keyword is also accepted + # - `desc_positional` (`String`) — Field description; `description:` keyword is also accepted + # - `kwargs` (`Hash`) — Keywords for defining the field. Any not documented here will be passed to your base field class where they must be handled. + # - `definition_block` (`Proc`) — an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new [Field](rdoc-ref:Field). + # + # **Options** + # + # - `kwargs.:name` (`Symbol`) — The underscore-cased version of this field name (will be camelized for the GraphQL API); positional argument also accepted + # - `kwargs.:type` (`Class, GraphQL::BaseType, Array`) — The return type of this field; positional argument is also accepted + # - `kwargs.:null` (`Boolean`) — (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null` + # - `kwargs.:description` (`String`) — Field description; positional argument also accepted + # - `kwargs.:comment` (`String`) — Field comment + # - `kwargs.:deprecation_reason` (`String`) — If present, the field is marked "deprecated" with this message + # - `kwargs.:method` (`Symbol`) — The method to call on the underlying object to resolve this field (defaults to `name`) + # - `kwargs.: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`) + # - `kwargs.:dig` (`Array`) — The nested hash keys to lookup on the underlying hash to resolve this field using dig + # - `kwargs.:resolver_method` (`Symbol, true`) — The method on the type to call to resolve this field (defaults to `name`) + # - `kwargs.:resolve_static` (`Symbol, true`) — Used by `Schema.execute_next` to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments` + # - `kwargs.:resolve_batch` (`Symbol, true`) — Used by `Schema.execute_next` map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`. + # - `kwargs.:resolve_each` (`Symbol, true`) — Used by `Schema.execute_next` to get a value value for each item. Called on the owner type class with `object, context, **arguments`. + # - `kwargs.:resolve_legacy_instance_method` (`Symbol, true`) — Used by `Schema.execute_next` to get a value value for each item. Calls an instance method on the object type class. + # - `kwargs.:connection` (`Boolean`) — `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name + # - `kwargs.:connection_extension` (`Class`) — The extension to add, to implement connections. If `nil`, no extension is added. + # - `kwargs.:max_page_size` (`Integer, nil`) — For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results. + # - `kwargs.:default_page_size` (`Integer, nil`) — For connections, the default number of items to return from this field, or `nil` to return unlimited results. + # - `kwargs.:introspection` (`Boolean`) — If true, this field will be marked as `#introspection?` and the name may begin with `__` + # - `kwargs.:arguments` (`{String=>GraphQL::Schema::Argument, Hash}`) — Arguments for this field (may be added in the block, also) + # - `kwargs.:camelize` (`Boolean`) — If true, the field name will be camelized when building the schema + # - `kwargs.:complexity` (`Numeric`) — When provided, set the complexity for this field + # - `kwargs.:scope` (`Boolean`) — If true, the return type's `.scope_items` method will be called on the return value + # - `kwargs.:subscription_scope` (`Symbol, String`) — A key in `context` which will be used to scope subscription payloads + # - `kwargs.:extensions` (`Array Object>>`) — Named extensions to apply to this field (see also [Field#extension](rdoc-ref:GraphQL::Schema::Field#extension)) + # - `kwargs.:directives` (`Hash{Class => Hash}`) — Directives to apply to this field + # - `kwargs.:trace` (`Boolean`) — If true, a [GraphQL::Tracing](rdoc-ref:GraphQL::Tracing) tracer will measure this scalar field + # - `kwargs.:broadcastable` (`Boolean`) — Whether or not this field can be distributed in subscription broadcasts + # - `kwargs.:ast_node` (`Language::Nodes::FieldDefinition, nil`) — If this schema was parsed from definition, this AST node defined the field + # - `kwargs.:method_conflict_warning` (`Boolean`) — If false, skip the warning if this field's method conflicts with a built-in method + # - `kwargs.:validates` (`Array`) — Configurations for validating this field + # - `kwargs.:fallback_value` (`Object`) — A fallback value if the method is not defined + # - `kwargs.:mutation` (`Class`) + # - `kwargs.:resolver` (`Class`) + # - `kwargs.:subscription` (`Class`) + # - `kwargs.:dynamic_introspection` (`Boolean`) — (Private, used by GraphQL-Ruby) + # - `kwargs.:relay_node_field` (`Boolean`) — (Private, used by GraphQL-Ruby) + # - `kwargs.:relay_nodes_field` (`Boolean`) — (Private, used by GraphQL-Ruby) + # - `kwargs.:dataload` (`Class, Hash`) — Shorthand for dataloader lookups + # - `kwargs.:extras` (`Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>`) — Extra arguments to be injected into the resolver for this field + # + # **Yields** + # + # - `field` (`GraphQL::Schema::Field`) — The newly-created field instance + # - `void` + # + # **Returns** + # + # - `GraphQL::Schema::Field` + # + # :call-seq: + # field(Symbol name_positional, Class | GraphQL::BaseType | Array type_positional, String desc_positional, Hash **kwargs, Proc &definition_block) -> GraphQL::Schema::Field def field(name_positional = nil, type_positional = nil, desc_positional = nil, **kwargs, &definition_block) resolver = kwargs.delete(:resolver) mutation = kwargs.delete(:mutation) @@ -93,23 +108,32 @@ def field(name_positional = nil, type_positional = nil, desc_positional = nil, * # A list of Ruby keywords. # - # @api private + # :nodoc: 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 + # :nodoc: 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 + # :nodoc: 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] + # + # **Parameters** + # + # - `field_defn` (`GraphQL::Schema::Field`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # add_field(GraphQL::Schema::Field field_defn, method_conflict_warning:) -> 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. @@ -137,7 +161,12 @@ def add_field(field_defn, method_conflict_warning: field_defn.method_conflict_wa nil end - # @return [Class] The class to initialize when adding fields to this kind of schema member + # **Returns** + # + # - `Class` — The class to initialize when adding fields to this kind of schema member + # + # :call-seq: + # field_class(new_field_class) -> Class def field_class(new_field_class = nil) if new_field_class @field_class = new_field_class @@ -160,19 +189,37 @@ def global_id_field(field_name, **kwargs) end end - # @param new_has_no_fields [Boolean] Call with `true` to make this Object type ignore the requirement to have any defined fields. - # @return [void] + # **Parameters** + # + # - `new_has_no_fields` (`Boolean`) — Call with `true` to make this Object type ignore the requirement to have any defined fields. + # + # **Returns** + # + # - `void` + # + # :call-seq: + # has_no_fields(bool new_has_no_fields) -> void def has_no_fields(new_has_no_fields) @has_no_fields = new_has_no_fields nil end - # @return [Boolean] `true` if `has_no_fields(true)` was configued + # **Returns** + # + # - `Boolean` — `true` if `has_no_fields(true)` was configued + # + # :call-seq: + # has_no_fields?() -> bool def has_no_fields? @has_no_fields end - # @return [Hash GraphQL::Schema::Field, Array>] Fields defined on this class _specifically_, not parent classes + # **Returns** + # + # - `Hash GraphQL::Schema::Field, Array>` — Fields defined on this class _specifically_, not parent classes + # + # :call-seq: + # own_fields() -> Hash[String, GraphQL::Schema::Field | Array[GraphQL::Schema::Field]] def own_fields @own_fields ||= {} end @@ -203,7 +250,12 @@ def get_field(field_name, context = GraphQL::Query::NullContext.instance) nil end - # @return [Hash GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields + # **Returns** + # + # - `Hash GraphQL::Schema::Field>` — Fields on this object, keyed by name, including inherited fields + # + # :call-seq: + # fields(context:) -> Hash[String, GraphQL::Schema::Field] def fields(context = GraphQL::Query::NullContext.instance) warden = Warden.from_context(context) # Local overrides take precedence over inherited fields @@ -242,7 +294,12 @@ def get_field(field_name, context = GraphQL::Query::NullContext.instance) nil end - # @return [Hash GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields + # **Returns** + # + # - `Hash GraphQL::Schema::Field>` — Fields on this object, keyed by name, including inherited fields + # + # :call-seq: + # fields(context:) -> Hash[String, GraphQL::Schema::Field] def fields(context = GraphQL::Query::NullContext.instance) # Objects need to check that the interface implementation is visible, too warden = Warden.from_context(context) @@ -313,8 +370,16 @@ def visible_interface_implementation?(type, context, warden) end end - # @param field_defn [GraphQL::Schema::Field] - # @return [String] A warning to give when this field definition might conflict with a built-in method + # **Parameters** + # + # - `field_defn` (`GraphQL::Schema::Field`) + # + # **Returns** + # + # - `String` — A warning to give when this field definition might conflict with a built-in method + # + # :call-seq: + # conflict_field_name_warning(GraphQL::Schema::Field field_defn) -> String 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 diff --git a/lib/graphql/schema/member/has_path.rb b/lib/graphql/schema/member/has_path.rb index a004023b093..f432b14952e 100644 --- a/lib/graphql/schema/member/has_path.rb +++ b/lib/graphql/schema/member/has_path.rb @@ -4,7 +4,12 @@ module GraphQL class Schema class Member module HasPath - # @return [String] A description of this member's place in the GraphQL schema + # **Returns** + # + # - `String` — A description of this member's place in the GraphQL schema + # + # :call-seq: + # path() -> String def path path_str = if self.respond_to?(:graphql_name) self.graphql_name diff --git a/lib/graphql/schema/member/has_validators.rb b/lib/graphql/schema/member/has_validators.rb index 7e877f93259..6d3a2a7e921 100644 --- a/lib/graphql/schema/member/has_validators.rb +++ b/lib/graphql/schema/member/has_validators.rb @@ -5,10 +5,19 @@ class Member module HasValidators include GraphQL::EmptyObjects - # Build {GraphQL::Schema::Validator}s based on the given configuration + # Build [GraphQL::Schema::Validator](rdoc-ref:GraphQL::Schema::Validator)s based on the given configuration # and use them for this schema member - # @param validation_config [Hash{Symbol => Hash}] - # @return [void] + # + # **Parameters** + # + # - `validation_config` (`Hash{Symbol => Hash}`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # validates(Hash[Symbol, Hash] validation_config) -> void def validates(validation_config) new_validators = GraphQL::Schema::Validator.from_config(self, validation_config) @own_validators ||= [] @@ -16,7 +25,12 @@ def validates(validation_config) nil end - # @return [Array] + # **Returns** + # + # - `Array` + # + # :call-seq: + # validators() -> Array[GraphQL::Schema::Validator] def validators @own_validators || EMPTY_ARRAY end diff --git a/lib/graphql/schema/member/scoped.rb b/lib/graphql/schema/member/scoped.rb index 1e676cdbe02..00ca07fb304 100644 --- a/lib/graphql/schema/member/scoped.rb +++ b/lib/graphql/schema/member/scoped.rb @@ -9,9 +9,17 @@ module Scoped # # 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 + # **Parameters** + # + # - `items` (`Object`) — Some list-like object (eg, Array, ActiveRecord::Relation) + # - `context` (`GraphQL::Query::Context`) + # + # **Returns** + # + # - `Object` — Another list-like object, scoped to the current context + # + # :call-seq: + # scope_items(Object items, GraphQL::Query::Context context) -> Object def scope_items(items, context) items end diff --git a/lib/graphql/schema/member/type_system_helpers.rb b/lib/graphql/schema/member/type_system_helpers.rb index 109325747a6..fb226d08dbe 100644 --- a/lib/graphql/schema/member/type_system_helpers.rb +++ b/lib/graphql/schema/member/type_system_helpers.rb @@ -10,7 +10,12 @@ def initialize(...) @to_list_type ||= nil end - # @return [Schema::NonNull] Make a non-null-type representation of this type + # **Returns** + # + # - `Schema::NonNull` — Make a non-null-type representation of this type + # + # :call-seq: + # to_non_null_type() -> Schema::NonNull def to_non_null_type @to_non_null_type || begin t = GraphQL::Schema::NonNull.new(self) @@ -22,7 +27,12 @@ def to_non_null_type end end - # @return [Schema::List] Make a list-type representation of this type + # **Returns** + # + # - `Schema::List` — Make a list-type representation of this type + # + # :call-seq: + # to_list_type() -> Schema::List def to_list_type @to_list_type || begin t = GraphQL::Schema::List.new(self) @@ -34,12 +44,22 @@ def to_list_type end end - # @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable. + # **Returns** + # + # - `Boolean` — true if this is a non-nullable type. A nullable list of non-nullables is considered nullable. + # + # :call-seq: + # non_null?() -> bool def non_null? false end - # @return [Boolean] true if this is a list type. A non-nullable list is considered a list. + # **Returns** + # + # - `Boolean` — true if this is a list type. A non-nullable list is considered a list. + # + # :call-seq: + # list?() -> bool def list? false end @@ -48,7 +68,12 @@ def to_type_signature graphql_name end - # @return [GraphQL::TypeKinds::TypeKind] + # **Returns** + # + # - `GraphQL::TypeKinds::TypeKind` + # + # :call-seq: + # kind() -> GraphQL::TypeKinds::TypeKind def kind raise GraphQL::RequiredImplementationMissingError, "No `.kind` defined for #{self}" end diff --git a/lib/graphql/schema/mutation.rb b/lib/graphql/schema/mutation.rb index b0ba8f3f5ef..4aeca68eaed 100644 --- a/lib/graphql/schema/mutation.rb +++ b/lib/graphql/schema/mutation.rb @@ -8,62 +8,65 @@ class Schema # 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. + # See [GraphQL::Schema::RelayClassicMutation](rdoc-ref: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 + # **Examples** # - # field :comment, Types::Comment, null: true - # field :errors, [String], null: false + # **Example: Creating a comment** # - # 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 + # ```ruby + # # Define the mutation: + # class Mutations::CreateComment < GraphQL::Schema::Mutation + # argument :body, String, required: true + # argument :post_id, ID, required: true # - # # Hook it up to your mutation: - # class Types::Mutation < GraphQL::Schema::Object - # field :create_comment, mutation: Mutations::CreateComment - # end + # field :comment, Types::Comment, null: true + # field :errors, [String], null: false # - # # Call it from GraphQL: - # result = MySchema.execute <<-GRAPHQL - # mutation { - # createComment(postId: "1", body: "Nice Post!") { - # errors - # comment { - # body - # author { - # login - # } - # } - # } - # } - # GRAPHQL + # 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 - # @api private - def call_resolve(_args_hash) + def call_resolve(_args_hash) # :nodoc: # Clear any cached values from `loads` or authorization: dataloader.clear_cache super diff --git a/lib/graphql/schema/non_null.rb b/lib/graphql/schema/non_null.rb index 92267f707ae..d5c05bb182e 100644 --- a/lib/graphql/schema/non_null.rb +++ b/lib/graphql/schema/non_null.rb @@ -3,22 +3,37 @@ 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} + # Wraps a [Schema::Member](rdoc-ref:Schema::Member) when it is required. + # See [Schema::Member::TypeSystemHelpers#to_non_null_type](rdoc-ref:Schema::Member::TypeSystemHelpers#to_non_null_type) class NonNull < GraphQL::Schema::Wrapper include Schema::Member::ValidatesInput - # @return [GraphQL::TypeKinds::NON_NULL] + # **Returns** + # + # - `GraphQL::TypeKinds::NON_NULL` + # + # :call-seq: + # kind() -> GraphQL::TypeKinds::NON_NULL def kind GraphQL::TypeKinds::NON_NULL end - # @return [true] + # **Returns** + # + # - `true` + # + # :call-seq: + # non_null?() -> true def non_null? true end - # @return [Boolean] True if this type wraps a list type + # **Returns** + # + # - `Boolean` — True if this type wraps a list type + # + # :call-seq: + # list?() -> bool def list? @of_type.list? end diff --git a/lib/graphql/schema/object.rb b/lib/graphql/schema/object.rb index b7282999870..e09fd9f54f8 100644 --- a/lib/graphql/schema/object.rb +++ b/lib/graphql/schema/object.rb @@ -18,13 +18,28 @@ def initialize(object_type) end end - # @return [Object] the application object this type is wrapping + # **Returns** + # + # - `Object` — the application object this type is wrapping + # + # :call-seq: + # object -> Object attr_reader :object - # @return [GraphQL::Query::Context] the context instance for this query + # **Returns** + # + # - `GraphQL::Query::Context` — the context instance for this query + # + # :call-seq: + # context -> GraphQL::Query::Context attr_reader :context - # @return [GraphQL::Dataloader] + # **Returns** + # + # - `GraphQL::Dataloader` + # + # :call-seq: + # dataloader() -> GraphQL::Dataloader def dataloader context.dataloader end @@ -36,8 +51,8 @@ def raw_value(obj) end class << self - # This is protected so that we can be sure callers use the public method, {.authorized_new} - # @see authorized_new to make instances + # This is protected so that we can be sure callers use the public method, [.authorized_new](rdoc-ref:.authorized_new) + # See [authorized_new](rdoc-ref:authorized_new) to make instances protected :new def wrap_scoped(object, context) @@ -54,18 +69,29 @@ def wrap(object, context) # # Probably only the framework should call this method. # - # This might return a {GraphQL::Execution::Lazy} if the user-provided `.authorized?` + # This might return a [GraphQL::Execution::Lazy](rdoc-ref: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 + # The reason that the auth check is in this wrapper method instead of [.new](rdoc-ref:.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} + # It would be a headache to try to maintain Promise-y state inside a [Schema::Object](rdoc-ref: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` + # **Parameters** + # + # - `object` (`Object`) — The thing wrapped by this object + # - `context` (`GraphQL::Query::Context`) + # + # **Returns** + # + # - `GraphQL::Schema::Object, GraphQL::Execution::Lazy` + # + # **Raises** + # + # - `GraphQL::UnauthorizedError` — if the user-provided hook returns `false` + # + # :call-seq: + # authorized_new(Object object, GraphQL::Query::Context context) -> GraphQL::Schema::Object | GraphQL::Execution::Lazy | GraphQL::UnauthorizedError def authorized_new(object, context) context.query.current_trace.begin_authorized(self, object, context) begin diff --git a/lib/graphql/schema/printer.rb b/lib/graphql/schema/printer.rb index b058f6cbb90..2ceba5f8af8 100644 --- a/lib/graphql/schema/printer.rb +++ b/lib/graphql/schema/printer.rb @@ -1,42 +1,57 @@ # frozen_string_literal: true module GraphQL class Schema - # Used to convert your {GraphQL::Schema} to a GraphQL schema string + # Used to convert your [GraphQL::Schema](rdoc-ref:GraphQL::Schema) to a GraphQL schema string # - # @example print your schema to standard output (via helper) - # puts GraphQL::Schema::Printer.print_schema(MySchema) + # **Examples** # - # @example print your schema to standard output - # puts GraphQL::Schema::Printer.new(MySchema).print_schema + # **Example: print your schema to standard output (via helper)** # - # @example print a single type to standard output - # class Types::Query < GraphQL::Schema::Object - # description "The query root of this schema" + # ```ruby + # puts GraphQL::Schema::Printer.print_schema(MySchema) + # ``` # - # field :post, Types::Post, null: true - # end + # **Example: print your schema to standard output** # - # class Types::Post < GraphQL::Schema::Object - # description "A blog post" + # ```ruby + # puts GraphQL::Schema::Printer.new(MySchema).print_schema + # ``` # - # field :id, ID, null: false - # field :title, String, null: false - # field :body, String, null: false - # end + # **Example: print a single type to standard output** # - # class MySchema < GraphQL::Schema - # query(Types::Query) - # end + # ```ruby + # class Types::Query < GraphQL::Schema::Object + # description "The query root of this schema" # - # printer = GraphQL::Schema::Printer.new(MySchema) - # puts printer.print_type(Types::Post) + # field :post, Types::Post, null: true + # end # + # class Types::Post < GraphQL::Schema::Object + # description "A blog post" + # + # field :id, ID, null: false + # field :title, String, null: false + # field :body, String, null: false + # end + # + # class MySchema < GraphQL::Schema + # query(Types::Query) + # end + # + # printer = GraphQL::Schema::Printer.new(MySchema) + # puts printer.print_type(Types::Post) + # ``` class Printer < GraphQL::Language::Printer attr_reader :schema, :warden - # @param schema [GraphQL::Schema] - # @param context [Hash] - # @param introspection [Boolean] Should include the introspection types in the string? + # **Parameters** + # + # - `schema` (`GraphQL::Schema`) + # - `context` (`Hash`) + # - `introspection` (`Boolean`) — Should include the introspection types in the string? + # + # :call-seq: + # initialize(GraphQL::Schema schema, Hash context:, bool introspection:) def initialize(schema, context: nil, introspection: false) @document_from_schema = GraphQL::Language::DocumentFromSchemaDefinition.new( schema, @@ -75,10 +90,16 @@ def self.visible?(member, _ctx) 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)>] + # + # **Parameters** + # + # - `schema` (`GraphQL::Schema`) + # - `context` (`Hash`) + # - `only` (`<#call(member, ctx)>`) + # - `except` (`<#call(member, ctx)>`) + # + # :call-seq: + # print_schema(GraphQL::Schema schema, **args) def self.print_schema(schema, **args) printer = new(schema, **args) printer.print_schema diff --git a/lib/graphql/schema/relay_classic_mutation.rb b/lib/graphql/schema/relay_classic_mutation.rb index e83de7319ad..a1eeb1dc74f 100644 --- a/lib/graphql/schema/relay_classic_mutation.rb +++ b/lib/graphql/schema/relay_classic_mutation.rb @@ -17,8 +17,7 @@ class Schema # - 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. - # + # See [GraphQL::Schema::Mutation](rdoc-ref:GraphQL::Schema::Mutation) for an example, it's basically the same. class RelayClassicMutation < GraphQL::Schema::Mutation include GraphQL::Schema::HasSingleInputArgument diff --git a/lib/graphql/schema/resolver.rb b/lib/graphql/schema/resolver.rb index 6a9c741e675..a84be9feced 100644 --- a/lib/graphql/schema/resolver.rb +++ b/lib/graphql/schema/resolver.rb @@ -16,8 +16,8 @@ class Schema # # A resolver's configuration may be overridden with other keywords in the `field(...)` call. # - # @see {GraphQL::Schema::Mutation} for a concrete subclass of `Resolver`. - # @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function` + # See [GraphQL::Schema::Mutation](rdoc-ref:GraphQL::Schema::Mutation) for a concrete subclass of `Resolver`. + # `Resolver` is a replacement for the former `GraphQL::Function` API. class Resolver include Schema::Member::GraphQLTypeNames # Really we only need description & comment from here, but: @@ -31,9 +31,14 @@ class Resolver include Schema::Member::HasDataloader extend Schema::Member::HasDeprecationReason - # @param object [Object] The application object that this field is being resolved on - # @param context [GraphQL::Query::Context] - # @param field [GraphQL::Schema::Field] + # **Parameters** + # + # - `object` (`Object`) — The application object that this field is being resolved on + # - `context` (`GraphQL::Query::Context`) + # - `field` (`GraphQL::Schema::Field`) + # + # :call-seq: + # initialize(Object object:, GraphQL::Query::Context context:, GraphQL::Schema::Field field:) def initialize(object:, context:, field:) @object = object @context = context @@ -48,13 +53,28 @@ def initialize(object:, context:, field:) attr_accessor :exec_result, :exec_index, :field_resolve_step, :raw_arguments - # @return [Object] The application object this field is being resolved on + # **Returns** + # + # - `Object` — The application object this field is being resolved on + # + # :call-seq: + # object -> Object attr_accessor :object - # @return [GraphQL::Query::Context] + # **Returns** + # + # - `GraphQL::Query::Context` + # + # :call-seq: + # context -> GraphQL::Query::Context attr_reader :context - # @return [GraphQL::Schema::Field] + # **Returns** + # + # - `GraphQL::Schema::Field` + # + # :call-seq: + # field -> GraphQL::Schema::Field attr_reader :field attr_writer :prepared_arguments @@ -132,8 +152,7 @@ def arguments # 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) + def resolve_with_support(**args) # :nodoc: # First call the ready? hook which may raise raw_ready_val = if !args.empty? ready?(**args) @@ -183,8 +202,7 @@ def resolve_with_support(**args) end end - # @api private {GraphQL::Schema::Mutation} uses this to clear the dataloader cache - def call_resolve(args_hash) + def call_resolve(args_hash) # :nodoc: if !args_hash.empty? public_send(self.class.resolve_method, **args_hash) else @@ -193,7 +211,13 @@ def call_resolve(args_hash) end # Do the work. Everything happens here. - # @return [Object] An object corresponding to the return type + # + # **Returns** + # + # - `Object` — An object corresponding to the return type + # + # :call-seq: + # resolve(**args) -> Object def resolve(**args) raise GraphQL::RequiredImplementationMissingError, "#{self.class.name}#resolve should execute the field's logic" end @@ -204,10 +228,21 @@ def resolve(**args) # 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.) + # **Parameters** + # + # - `args` (`Hash`) — The input arguments, if there are any + # + # **Raises** + # + # - `GraphQL::ExecutionError` — To add an error to the response + # - `GraphQL::UnauthorizedError` — To signal an authorization failure + # + # **Returns** + # + # - `Boolean, early_return_data` — If `false`, execution will stop (and `early_return_data` will be returned instead, if present.) + # + # :call-seq: + # ready?(Hash **args) -> bool | early_return_data def ready?(**args) true end @@ -215,10 +250,22 @@ def ready?(**args) # 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.) + # + # **Parameters** + # + # - `inputs` (`Hash`) — The input arguments + # + # **Raises** + # + # - `GraphQL::ExecutionError` — To add an error to the response + # - `GraphQL::UnauthorizedError` — To signal an authorization failure + # + # **Returns** + # + # - `Boolean, early_return_data` — If `false`, execution will stop (and `early_return_data` will be returned instead, if present.) + # + # :call-seq: + # authorized?(Hash **inputs) -> bool | early_return_data def authorized?(**inputs) arg_owner = @field # || self.class args = context.types.arguments(arg_owner) @@ -231,10 +278,16 @@ def self.authorizes?(context) # Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type. # - # By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}. + # By default, the error is re-raised and passed along to {[Schema.unauthorized_object](rdoc-ref:Schema.unauthorized_object)}. # # Any value returned here will be used _instead of_ of the loaded object. - # @param err [GraphQL::UnauthorizedError] + # + # **Parameters** + # + # - `err` (`GraphQL::UnauthorizedError`) + # + # :call-seq: + # unauthorized_object(GraphQL::UnauthorizedError err) def unauthorized_object(err) raise err end @@ -309,7 +362,13 @@ def all_field_argument_definitions end # Default `:resolve` set below. - # @return [Symbol] The method to call on instances of this object to resolve the field + # + # **Returns** + # + # - `Symbol` — The method to call on instances of this object to resolve the field + # + # :call-seq: + # resolve_method(new_method) -> Symbol def resolve_method(new_method = nil) if new_method @resolve_method = new_method @@ -317,8 +376,8 @@ def resolve_method(new_method = nil) @resolve_method || (superclass.respond_to?(:resolve_method) ? superclass.resolve_method : :resolve) end - # Additional info injected into {#resolve} - # @see {GraphQL::Schema::Field#extras} + # Additional info injected into [resolve](rdoc-ref:#resolve) + # See [GraphQL::Schema::Field#extras](rdoc-ref:GraphQL::Schema::Field#extras) def extras(new_extras = nil) if new_extras @own_extras = new_extras @@ -330,8 +389,14 @@ def extras(new_extras = nil) # If `true` (default), then the return type for this resolver will be nullable. # If `false`, then the return type is non-null. # - # @see #type which sets the return type of this field and accepts a `null:` option - # @param allow_null [Boolean] Whether or not the response can be null + # See [type](rdoc-ref:GraphQL::Schema::Resolver::type) which sets the return type of this field and accepts a `null:` option + # + # **Parameters** + # + # - `allow_null` (`Boolean`) — Whether or not the response can be null + # + # :call-seq: + # null(bool allow_null) def null(allow_null = nil) if !allow_null.nil? @null = allow_null @@ -351,10 +416,19 @@ def resolver_method(new_method_name = nil) # 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. + # TODO unify with [null](rdoc-ref:GraphQL::Schema::Resolver::null) + # + # **Parameters** + # + # - `new_type` (`Class, Array, nil`) — If a type definition class is provided, it will be used as the return type of the field + # - `null` (`true, false`) — Whether or not the field may return `nil` + # + # **Returns** + # + # - `Class` — The type which this field returns. + # + # :call-seq: + # type(Class | Array[Class] | nil new_type, true | false null:) -> Class def type(new_type = nil, null: nil) if new_type if null.nil? @@ -374,7 +448,13 @@ def type(new_type = nil, null: nil) end # Specifies the complexity of the field. Defaults to `1` - # @return [Integer, Proc] + # + # **Returns** + # + # - `Integer, Proc` + # + # :call-seq: + # complexity(new_complexity) -> Integer | Proc def complexity(new_complexity = nil) if new_complexity @complexity = new_complexity @@ -386,7 +466,12 @@ def broadcastable(new_broadcastable) @broadcastable = new_broadcastable end - # @return [Boolean, nil] + # **Returns** + # + # - `Boolean, nil` + # + # :call-seq: + # broadcastable?() -> bool | nil def broadcastable? if defined?(@broadcastable) @broadcastable @@ -397,8 +482,17 @@ def broadcastable? # Get or set the `max_page_size:` which will be configured for fields using this resolver # (`nil` means "unlimited max page size".) - # @param max_page_size [Integer, nil] Set a new value - # @return [Integer, nil] The `max_page_size` assigned to fields that use this resolver + # + # **Parameters** + # + # - `max_page_size` (`Integer, nil`) — Set a new value + # + # **Returns** + # + # - `Integer, nil` — The `max_page_size` assigned to fields that use this resolver + # + # :call-seq: + # max_page_size(new_max_page_size) -> Integer | nil def max_page_size(new_max_page_size = NOT_CONFIGURED) if new_max_page_size != NOT_CONFIGURED @max_page_size = new_max_page_size @@ -411,15 +505,29 @@ def max_page_size(new_max_page_size = NOT_CONFIGURED) end end - # @return [Boolean] `true` if this resolver or a superclass has an assigned `max_page_size` + # **Returns** + # + # - `Boolean` — `true` if this resolver or a superclass has an assigned `max_page_size` + # + # :call-seq: + # has_max_page_size?() -> bool def has_max_page_size? (!!defined?(@max_page_size)) || (superclass.respond_to?(:has_max_page_size?) && superclass.has_max_page_size?) end # Get or set the `default_page_size:` which will be configured for fields using this resolver # (`nil` means "unlimited default page size".) - # @param default_page_size [Integer, nil] Set a new value - # @return [Integer, nil] The `default_page_size` assigned to fields that use this resolver + # + # **Parameters** + # + # - `default_page_size` (`Integer, nil`) — Set a new value + # + # **Returns** + # + # - `Integer, nil` — The `default_page_size` assigned to fields that use this resolver + # + # :call-seq: + # default_page_size(new_default_page_size) -> Integer | nil def default_page_size(new_default_page_size = NOT_CONFIGURED) if new_default_page_size != NOT_CONFIGURED @default_page_size = new_default_page_size @@ -432,7 +540,12 @@ def default_page_size(new_default_page_size = NOT_CONFIGURED) end end - # @return [Boolean] `true` if this resolver or a superclass has an assigned `default_page_size` + # **Returns** + # + # - `Boolean` — `true` if this resolver or a superclass has an assigned `default_page_size` + # + # :call-seq: + # has_default_page_size?() -> bool def has_default_page_size? (!!defined?(@default_page_size)) || (superclass.respond_to?(:has_default_page_size?) && superclass.has_default_page_size?) end @@ -444,7 +557,7 @@ def type_expr # 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 + # See [GraphQL::Schema::Argument](rdoc-ref:GraphQL::Schema::Argument) for the signature def argument(*args, **kwargs, &block) # Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation # so that we can support `#load_{x}` methods below. @@ -452,15 +565,20 @@ def argument(*args, **kwargs, &block) end # Registers new extension - # @param extension [Class] Extension class - # @param options [Hash] Optional extension options + # + # **Parameters** + # + # - `extension` (`Class`) — Extension class + # - `options` (`Hash`) — Optional extension options + # + # :call-seq: + # extension(Class extension, Hash **options) def extension(extension, **options) @own_extensions ||= [] @own_extensions << {extension => options} end - # @api private - def extensions + def extensions # :nodoc: own_exts = @own_extensions # Jump through some hoops to avoid creating arrays when we don't actually need them if superclass.respond_to?(:extensions) diff --git a/lib/graphql/schema/resolver/has_payload_type.rb b/lib/graphql/schema/resolver/has_payload_type.rb index 89a974164de..b80a1eb88f1 100644 --- a/lib/graphql/schema/resolver/has_payload_type.rb +++ b/lib/graphql/schema/resolver/has_payload_type.rb @@ -11,8 +11,17 @@ 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. + # + # **Parameters** + # + # - `new_payload_type` (`Class, nil`) — If a type definition class is provided, it will be used as the return type of the mutation field + # + # **Returns** + # + # - `Class` — The object type which this mutation returns. + # + # :call-seq: + # payload_type(Class | nil new_payload_type) -> Class def payload_type(new_payload_type = nil) if new_payload_type @payload_type = new_payload_type @@ -44,8 +53,17 @@ def field_class(new_class = nil) end # An object class to use for deriving return types - # @param new_class [Class, nil] Defaults to {GraphQL::Schema::Object} - # @return [Class] + # + # **Parameters** + # + # - `new_class` (`Class, nil`) — Defaults to [GraphQL::Schema::Object](rdoc-ref:GraphQL::Schema::Object) + # + # **Returns** + # + # - `Class` + # + # :call-seq: + # object_class(Class | nil new_class) -> Class def object_class(new_class = nil) if new_class if defined?(@payload_type) diff --git a/lib/graphql/schema/subscription.rb b/lib/graphql/schema/subscription.rb index b0e11a689da..c110dcf94eb 100644 --- a/lib/graphql/schema/subscription.rb +++ b/lib/graphql/schema/subscription.rb @@ -17,8 +17,7 @@ class Subscription < GraphQL::Schema::Resolver NO_UPDATE = :no_update null false - # @api private - def initialize(object:, context:, field:) + def initialize(object:, context:, field:) # :nodoc: super # Figure out whether this is an update or an initial subscription @mode = context.query.subscription_update? ? :update : :subscribe @@ -30,8 +29,7 @@ def initialize(object:, context:, field:) end end - # @api private - def call_resolve(args_hash) + def call_resolve(args_hash) # :nodoc: if @field_resolve_step.nil? super else @@ -60,8 +58,7 @@ def call_resolve(args_hash) end end - # @api private - def resolve_with_support(**args) + def resolve_with_support(**args) # :nodoc: @original_arguments = args # before `loads:` have been run result = nil unsubscribed = true @@ -96,8 +93,7 @@ def resolve(**args) end # Wrap the user-defined `#subscribe` hook - # @api private - def resolve_subscribe(**args) + def resolve_subscribe(**args) # :nodoc: ret_val = !args.empty? ? subscribe(**args) : subscribe if ret_val == :no_response context.skip @@ -114,8 +110,7 @@ def subscribe(args = {}) end # Wrap the user-provided `#update` hook - # @api private - def resolve_update(**args) + def resolve_update(**args) # :nodoc: ret_val = !args.empty? ? update(**args) : update if ret_val == NO_UPDATE context.namespace(:subscriptions)[:no_update] = true @@ -143,8 +138,17 @@ def load_application_object_failed(err) end # Call this to halt execution and remove this subscription from the system - # @param update_value [Object] if present, deliver this update before unsubscribing - # @return [void] + # + # **Parameters** + # + # - `update_value` (`Object`) — if present, deliver this update before unsubscribing + # + # **Returns** + # + # - `void` + # + # :call-seq: + # unsubscribe(Object update_value) -> void def unsubscribe(update_value = nil) context.namespace(:subscriptions)[:unsubscribed] = true err = EarlyUnsubscribe.new @@ -158,9 +162,18 @@ class EarlyUnsubscribe < GraphQL::RuntimeError # Call this method to provide a new subscription_scope; OR # call it without an argument to get the subscription_scope - # @param new_scope [Symbol] - # @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription. - # @return [Symbol] + # + # **Parameters** + # + # - `new_scope` (`Symbol`) + # - `optional` (`Boolean`) — If true, then don't require `scope:` to be provided to updates to this subscription. + # + # **Returns** + # + # - `Symbol` + # + # :call-seq: + # subscription_scope(Symbol new_scope, bool optional:) -> Symbol def self.subscription_scope(new_scope = NOT_CONFIGURED, optional: false) if new_scope != NOT_CONFIGURED @subscription_scope = new_scope @@ -188,14 +201,23 @@ def self.subscription_scope_optional? # In that implementation, only `.trigger` calls with _exact matches_ result in updates to subscribers. # # To implement a filtered stream-type subscription flow, override this method to return a string with field name and subscription scope. - # Then, implement {#update} to compare its arguments to the current `object` and return {NO_UPDATE} when an + # Then, implement [update](rdoc-ref:#update) to compare its arguments to the current `object` and return [NO_UPDATE](rdoc-ref:NO_UPDATE) when an # update should be filtered out. # - # @see {#update} for how to skip updates when an event comes with a matching topic. - # @param arguments [Hash Object>] The arguments for this topic, in GraphQL-style (camelized strings) - # @param field [GraphQL::Schema::Field] - # @param scope [Object, nil] A value corresponding to `.trigger(... scope:)` (for updates) or the `subscription_scope` found in `context` (for initial subscriptions). - # @return [String] An identifier corresponding to a stream of updates + # See [update](rdoc-ref:#update) for how to skip updates when an event comes with a matching topic. + # + # **Parameters** + # + # - `arguments` (`Hash Object>`) — The arguments for this topic, in GraphQL-style (camelized strings) + # - `field` (`GraphQL::Schema::Field`) + # - `scope` (`Object, nil`) — A value corresponding to `.trigger(... scope:)` (for updates) or the `subscription_scope` found in `context` (for initial subscriptions). + # + # **Returns** + # + # - `String` — An identifier corresponding to a stream of updates + # + # :call-seq: + # topic_for(Hash[String, Object] arguments:, GraphQL::Schema::Field field:, Object | nil scope:) -> String def self.topic_for(arguments:, field:, scope:) Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments]) end @@ -205,10 +227,16 @@ def self.topic_for(arguments:, field:, scope:) # but if you need to commit the subscription during `#subscribe`, you can call it there. # (This method also sets a flag showing that this subscription was already written.) # - # If you call this method yourself, you may also need to {#unsubscribe} + # If you call this method yourself, you may also need to [unsubscribe](rdoc-ref:#unsubscribe) # or call `subscriptions.delete_subscription` to clean up the database if the query crashes with an error # later in execution. - # @return [void] + # + # **Returns** + # + # - `void` + # + # :call-seq: + # write_subscription() -> void def write_subscription if subscription_written? raise GraphQL::Error, "`write_subscription` was called but `#{self.class}#subscription_written?` is already true. Remove a call to `write subscription`." @@ -219,12 +247,22 @@ def write_subscription nil end - # @return [Boolean] `true` if {#write_subscription} was called already + # **Returns** + # + # - `Boolean` — `true` if [write subscription](rdoc-ref:#write_subscription) was called already + # + # :call-seq: + # subscription_written?() -> bool def subscription_written? @subscription_written end - # @return [Subscriptions::Event] This object is used as a representation of this subscription for the backend + # **Returns** + # + # - `Subscriptions::Event` — This object is used as a representation of this subscription for the backend + # + # :call-seq: + # event() -> Subscriptions::Event def event @event ||= begin if @original_arguments.nil? && @field_resolve_step diff --git a/lib/graphql/schema/timeout.rb b/lib/graphql/schema/timeout.rb index fae8ca44820..aa09ad68a5c 100644 --- a/lib/graphql/schema/timeout.rb +++ b/lib/graphql/schema/timeout.rb @@ -15,23 +15,30 @@ class Schema # 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 + # **Examples** # - # @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 + # **Example: Stop resolving fields after 2 seconds** + # + # ```ruby + # class MySchema < GraphQL::Schema + # use GraphQL::Schema::Timeout, max_seconds: 2 + # end + # ``` + # + # **Example: Notifying Bugsnag and logging a timeout** # - # class MySchema < GraphQL::Schema - # use MyTimeout, max_seconds: 2 + # ```ruby + # 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, max_seconds: nil) timeout = self.new(max_seconds: max_seconds) @@ -43,7 +50,12 @@ def initialize(max_seconds:) end module Trace - # @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields + # **Parameters** + # + # - `max_seconds` (`Numeric`) — how many seconds the query should be allowed to resolve new fields + # + # :call-seq: + # initialize(timeout:, **rest) def initialize(timeout:, **rest) @timeout = timeout super @@ -97,23 +109,46 @@ def begin_execute_field(field, _arguments, _objects, query) # 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 [Numeric, false] The number of seconds after which to interrupt query execution and call {#handle_error}, or `false` to bypass the timeout. + # **Parameters** + # + # - `query` (`GraphQL::Query`) — The query that's about to run + # + # **Returns** + # + # - `Numeric, false` — The number of seconds after which to interrupt query execution and call `handle_error`, or `false` to bypass the timeout. + # + # :call-seq: + # max_seconds(GraphQL::Query query) -> Numeric | false def max_seconds(query) @max_seconds end # Invoked when a query times out. - # @param error [GraphQL::Schema::Timeout::TimeoutError] - # @param query [GraphQL::Error] + # + # **Parameters** + # + # - `error` (`GraphQL::Schema::Timeout::TimeoutError`) + # - `query` (`GraphQL::Error`) + # + # :call-seq: + # handle_timeout(GraphQL::Schema::Timeout::TimeoutError error, GraphQL::Error query) def handle_timeout(error, query) # override to do something interesting end - # Call this method (eg, from {#handle_timeout}) to disable timeout tracking + # Call this method (eg, from [handle timeout](rdoc-ref:#handle_timeout)) to disable timeout tracking # for the given query. - # @param query [GraphQL::Query] - # @return [void] + # + # **Parameters** + # + # - `query` (`GraphQL::Query`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # disable_timeout(GraphQL::Query query) -> void def disable_timeout(query) query.context.namespace(self)[:state] = false nil diff --git a/lib/graphql/schema/type_expression.rb b/lib/graphql/schema/type_expression.rb index 5ade5c70145..a1ebab006c0 100644 --- a/lib/graphql/schema/type_expression.rb +++ b/lib/graphql/schema/type_expression.rb @@ -1,13 +1,21 @@ # frozen_string_literal: true module GraphQL class Schema - # @api private - module TypeExpression + module TypeExpression # :nodoc: # Fetch a type from a type map by its AST specification. # Return `nil` if not found. - # @param type_owner [#type] A thing for looking up types by name - # @param ast_node [GraphQL::Language::Nodes::AbstractNode] - # @return [Class, GraphQL::Schema::NonNull, GraphQL::Schema:List] + # + # **Parameters** + # + # - `type_owner` (`#type`) — A thing for looking up types by name + # - `ast_node` (`GraphQL::Language::Nodes::AbstractNode`) + # + # **Returns** + # + # - `Class, GraphQL::Schema::NonNull, GraphQL::Schema:List` + # + # :call-seq: + # build_type(#type type_owner, GraphQL::Language::Nodes::AbstractNode ast_node) -> Class | GraphQL::Schema::NonNull | GraphQL::Schema:List def self.build_type(type_owner, ast_node) case ast_node when GraphQL::Language::Nodes::TypeName diff --git a/lib/graphql/schema/type_membership.rb b/lib/graphql/schema/type_membership.rb index 6f25da49db0..bf27966c789 100644 --- a/lib/graphql/schema/type_membership.rb +++ b/lib/graphql/schema/type_membership.rb @@ -5,28 +5,53 @@ class Schema # This class joins an object type to an abstract type (interface or union) of which # it is a member. class TypeMembership - # @return [Class] + # **Returns** + # + # - `Class` + # + # :call-seq: + # object_type -> Class[GraphQL::Schema::Object] attr_accessor :object_type - # @return [Class, Module] + # **Returns** + # + # - `Class, Module` + # + # :call-seq: + # abstract_type -> Class[GraphQL::Schema::Union] | Module[GraphQL::Schema::Interface] attr_reader :abstract_type - # @return [Hash] + # **Returns** + # + # - `Hash` + # + # :call-seq: + # options -> Hash attr_reader :options - # Called when an object is hooked up to an abstract type, such as {Schema::Union.possible_types} - # or {Schema::Object.implements} (for interfaces). + # Called when an object is hooked up to an abstract type, such as [Schema::Union.possible_types](rdoc-ref:Schema::Union.possible_types) + # or `Schema::Object.implements` (for interfaces). + # + # **Parameters** + # + # - `abstract_type` (`Class, Module`) + # - `object_type` (`Class`) + # - `options` (`Hash`) — Any options passed to `.possible_types` or `.implements` # - # @param abstract_type [Class, Module] - # @param object_type [Class] - # @param options [Hash] Any options passed to `.possible_types` or `.implements` + # :call-seq: + # initialize(Class[GraphQL::Schema::Union] | Module[GraphQL::Schema::Interface] abstract_type, Class[GraphQL::Schema::Object] object_type, Hash **options) 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} + # **Returns** + # + # - `Boolean` — if false, [object type](rdoc-ref:#object_type) will be treated as _not_ a member of [abstract type](rdoc-ref:#abstract_type) + # + # :call-seq: + # visible?(ctx) -> bool def visible?(ctx) warden = Warden.from_context(ctx) (@object_type.respond_to?(:visible?) ? warden.visible_type?(@object_type, ctx) : true) && diff --git a/lib/graphql/schema/union.rb b/lib/graphql/schema/union.rb index f0ad4feec7f..95a936d035b 100644 --- a/lib/graphql/schema/union.rb +++ b/lib/graphql/schema/union.rb @@ -51,8 +51,7 @@ def type_memberships # 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) + def assign_type_membership_object_type(object_type) # :nodoc: assert_valid_union_member(object_type) type_memberships.each { |tm| possible_type = tm.object_type diff --git a/lib/graphql/schema/unique_within_type.rb b/lib/graphql/schema/unique_within_type.rb index 55c5f715296..4155bf99774 100644 --- a/lib/graphql/schema/unique_within_type.rb +++ b/lib/graphql/schema/unique_within_type.rb @@ -11,9 +11,17 @@ class << self module_function - # @param type_name [String] - # @param object_value [Any] - # @return [String] a unique, opaque ID generated as a function of the two inputs + # **Parameters** + # + # - `type_name` (`String`) + # - `object_value` (`Any`) + # + # **Returns** + # + # - `String` — a unique, opaque ID generated as a function of the two inputs + # + # :call-seq: + # encode(String type_name, Any object_value, separator:) -> String def encode(type_name, object_value, separator: self.default_id_separator) object_value_str = object_value.to_s @@ -24,8 +32,16 @@ def encode(type_name, object_value, separator: self.default_id_separator) 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} + # **Parameters** + # + # - `node_id` (`String`) — A unique ID generated by [.encode](rdoc-ref:.encode) + # + # **Returns** + # + # - `Array<(String, String)>` — The type name & value passed to [.encode](rdoc-ref:.encode) + # + # :call-seq: + # decode(String node_id, separator:) -> Array[(String, String)] def decode(node_id, separator: self.default_id_separator) GraphQL::Schema::Base64Encoder.decode(node_id).split(separator, 2) end diff --git a/lib/graphql/schema/validator.rb b/lib/graphql/schema/validator.rb index dd3b606cff1..7b1ac552591 100644 --- a/lib/graphql/schema/validator.rb +++ b/lib/graphql/schema/validator.rb @@ -4,22 +4,41 @@ module GraphQL class Schema class Validator # The thing being validated - # @return [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class] + # + # **Returns** + # + # - `GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class` + # + # :call-seq: + # validated -> GraphQL::Schema::Argument | GraphQL::Schema::Field | GraphQL::Schema::Resolver | Class[GraphQL::Schema::InputObject] attr_reader :validated - # @param validated [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class] The argument or argument owner this validator is attached to - # @param allow_blank [Boolean] if `true`, then objects that respond to `.blank?` and return true for `.blank?` will skip this validation - # @param allow_null [Boolean] if `true`, then incoming `null`s will skip this validation + # **Parameters** + # + # - `validated` (`GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class`) — The argument or argument owner this validator is attached to + # - `allow_blank` (`Boolean`) — if `true`, then objects that respond to `.blank?` and return true for `.blank?` will skip this validation + # - `allow_null` (`Boolean`) — if `true`, then incoming `null`s will skip this validation + # + # :call-seq: + # initialize(GraphQL::Schema::Argument | GraphQL::Schema::Field | GraphQL::Schema::Resolver | Class[GraphQL::Schema::InputObject] validated:, bool allow_blank:, bool allow_null:) def initialize(validated:, allow_blank: false, allow_null: false) @validated = validated @allow_blank = allow_blank @allow_null = allow_null end - # @param object [Object] The application object that this argument's field is being resolved for - # @param context [GraphQL::Query::Context] - # @param value [Object] The client-provided value for this argument (after parsing and coercing by the input type) - # @return [nil, Array, String] Error message or messages to add + # **Parameters** + # + # - `object` (`Object`) — The application object that this argument's field is being resolved for + # - `context` (`GraphQL::Query::Context`) + # - `value` (`Object`) — The client-provided value for this argument (after parsing and coercing by the input type) + # + # **Returns** + # + # - `nil, Array, String` — Error message or messages to add + # + # :call-seq: + # validate(Object object, GraphQL::Query::Context context, Object value) -> nil | Array[String] | String def validate(object, context, value) raise GraphQL::RequiredImplementationMissingError, "Validator classes should implement #validate" end @@ -34,7 +53,12 @@ def partial_format(string, substitutions) string end - # @return [Object] The current value to use for validation, based on `config_value` from configuration time. If a Proc is given, this calls it and returns it. + # **Returns** + # + # - `Object` — The current value to use for validation, based on `config_value` from configuration time. If a Proc is given, this calls it and returns it. + # + # :call-seq: + # validation_parameter(config_value) -> Object def validation_parameter(config_value) if config_value.is_a?(Proc) config_value.call @@ -43,15 +67,28 @@ def validation_parameter(config_value) end end - # @return [Boolean] `true` if `value` is `nil` and this validator has `allow_null: true` or if value is `.blank?` and this validator has `allow_blank: true` + # **Returns** + # + # - `Boolean` — `true` if `value` is `nil` and this validator has `allow_null: true` or if value is `.blank?` and this validator has `allow_blank: true` + # + # :call-seq: + # permitted_empty_value?(value) -> bool def permitted_empty_value?(value) (value.nil? && @allow_null) || (@allow_blank && value.respond_to?(:blank?) && value.blank?) end - # @param schema_member [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class] - # @param validates_hash [Hash{Symbol => Hash}, Hash{Class => Hash} nil] A configuration passed as `validates:` - # @return [Array] + # **Parameters** + # + # - `schema_member` (`GraphQL::Schema::Field, GraphQL::Schema::Argument, Class`) + # - `validates_hash` (`Hash{Symbol => Hash}, Hash{Class => Hash} nil`) — A configuration passed as `validates:` + # + # **Returns** + # + # - `Array` + # + # :call-seq: + # from_config(GraphQL::Schema::Field | GraphQL::Schema::Argument | Class[GraphQL::Schema::InputObject] schema_member, Hash[Symbol, Hash] | Hash[Class, Hash] nil validates_hash) -> Array[Validator] def self.from_config(schema_member, validates_hash) if validates_hash.nil? || validates_hash.empty? EMPTY_ARRAY @@ -89,17 +126,35 @@ def self.from_config(schema_member, validates_hash) # Add `validator_class` to be initialized when `validates:` is given `name`. # (It's initialized with whatever options are given by the key `name`). - # @param name [Symbol] - # @param validator_class [Class] - # @return [void] + # + # **Parameters** + # + # - `name` (`Symbol`) + # - `validator_class` (`Class`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # install(Symbol name, Class validator_class) -> void def self.install(name, validator_class) all_validators[name] = validator_class nil end - # Remove whatever validator class is {.install}ed at `name`, if there is one - # @param name [Symbol] - # @return [void] + # Remove whatever validator class is [.install](rdoc-ref:.install)ed at `name`, if there is one + # + # **Parameters** + # + # - `name` (`Symbol`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # uninstall(Symbol name) -> void def self.uninstall(name) all_validators.delete(name) nil @@ -122,12 +177,23 @@ def initialize(errors:) end end - # @param validators [Array] - # @param object [Object] - # @param context [Query::Context] - # @param value [Object] - # @return [void] - # @raises [ValidationFailedError] + # **Parameters** + # + # - `validators` (`Array`) + # - `object` (`Object`) + # - `context` (`Query::Context`) + # - `value` (`Object`) + # + # **Returns** + # + # - `void` + # + # **Raises** + # + # - `ValidationFailedError` + # + # :call-seq: + # validate!(Array[Validator] validators, Object object, Query::Context context, Object value, as:) -> void | ValidationFailedError def self.validate!(validators, object, context, value, as: nil) # Assuming the default case is no errors, reduce allocations in that case. # This will be replaced with a mutable array if we actually get any errors. diff --git a/lib/graphql/schema/validator/all_validator.rb b/lib/graphql/schema/validator/all_validator.rb index 5850144a02f..51bb6062c76 100644 --- a/lib/graphql/schema/validator/all_validator.rb +++ b/lib/graphql/schema/validator/all_validator.rb @@ -5,21 +5,28 @@ class Schema class Validator # Use this to validate each member of an array value. # - # @example validate format of all strings in an array + # **Examples** # - # argument :handles, [String], - # validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ } } } + # **Example: validate format of all strings in an array** # - # @example multiple validators can be combined + # ```ruby + # argument :handles, [String], + # validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ } } } + # ``` # - # argument :handles, [String], - # validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ }, length: { maximum: 32 } } } + # **Example: multiple validators can be combined** # - # @example any type can be used + # ```ruby + # argument :handles, [String], + # validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ }, length: { maximum: 32 } } } + # ``` # - # argument :choices, [Integer], - # validates: { all: { inclusion: { in: 1..12 } } } + # **Example: any type can be used** # + # ```ruby + # argument :choices, [Integer], + # validates: { all: { inclusion: { in: 1..12 } } } + # ``` class AllValidator < Validator def initialize(validated:, allow_blank: false, allow_null: false, **validators) super(validated: validated, allow_blank: allow_blank, allow_null: allow_null) diff --git a/lib/graphql/schema/validator/allow_blank_validator.rb b/lib/graphql/schema/validator/allow_blank_validator.rb index c2df2b5f672..c70ced2daf0 100644 --- a/lib/graphql/schema/validator/allow_blank_validator.rb +++ b/lib/graphql/schema/validator/allow_blank_validator.rb @@ -5,8 +5,13 @@ class Schema class Validator # Use this to specifically reject values that respond to `.blank?` and respond truthy for that method. # - # @example Require a non-empty string for an argument - # argument :name, String, required: true, validate: { allow_blank: false } + # **Examples** + # + # **Example: Require a non-empty string for an argument** + # + # ```ruby + # argument :name, String, required: true, validate: { allow_blank: false } + # ``` class AllowBlankValidator < Validator def initialize(allow_blank_positional = nil, allow_blank: nil, message: "%{validated} can't be blank", **default_options) @message = message diff --git a/lib/graphql/schema/validator/allow_null_validator.rb b/lib/graphql/schema/validator/allow_null_validator.rb index 9089f00f945..90ffde0b30c 100644 --- a/lib/graphql/schema/validator/allow_null_validator.rb +++ b/lib/graphql/schema/validator/allow_null_validator.rb @@ -5,8 +5,13 @@ class Schema class Validator # Use this to specifically reject or permit `nil` values (given as `null` from GraphQL). # - # @example require a non-null value for an argument if it is provided - # argument :name, String, required: false, validates: { allow_null: false } + # **Examples** + # + # **Example: require a non-null value for an argument if it is provided** + # + # ```ruby + # argument :name, String, required: false, validates: { allow_null: false } + # ``` class AllowNullValidator < Validator MESSAGE = "%{validated} can't be null" def initialize(allow_null_positional = nil, allow_null: nil, message: MESSAGE, **default_options) diff --git a/lib/graphql/schema/validator/exclusion_validator.rb b/lib/graphql/schema/validator/exclusion_validator.rb index 1e915eea8e1..2953bbe910f 100644 --- a/lib/graphql/schema/validator/exclusion_validator.rb +++ b/lib/graphql/schema/validator/exclusion_validator.rb @@ -5,14 +5,22 @@ class Schema class Validator # Use this to specifically reject values from an argument. # - # @example disallow certain values + # **Examples** # - # argument :favorite_non_prime, Integer, required: true, - # validates: { exclusion: { in: [2, 3, 5, 7, ... ]} } + # **Example: disallow certain values** # + # ```ruby + # argument :favorite_non_prime, Integer, required: true, + # validates: { exclusion: { in: [2, 3, 5, 7, ... ]} } + # ``` class ExclusionValidator < Validator - # @param message [String] - # @param in [Array] The values to reject + # **Parameters** + # + # - `message` (`String`) + # - `in` (`Array`) — The values to reject + # + # :call-seq: + # initialize(String message:, Array in:, **default_options) def initialize(message: "%{validated} is reserved", in:, **default_options) # `in` is a reserved word, so work around that @in_list = binding.local_variable_get(:in) diff --git a/lib/graphql/schema/validator/format_validator.rb b/lib/graphql/schema/validator/format_validator.rb index e790e3ecd17..815406eb93a 100644 --- a/lib/graphql/schema/validator/format_validator.rb +++ b/lib/graphql/schema/validator/format_validator.rb @@ -5,22 +5,32 @@ class Schema class Validator # Use this to assert that string values match (or don't match) the given RegExp. # - # @example requiring input to match a pattern + # **Examples** # - # argument :handle, String, required: true, - # validates: { format: { with: /\A[a-z0-9_]+\Z/ } } + # **Example: requiring input to match a pattern** # - # @example reject inputs that match a pattern + # ```ruby + # argument :handle, String, required: true, + # validates: { format: { with: /\A[a-z0-9_]+\Z/ } } + # ``` # - # argument :word_that_doesnt_begin_with_a_vowel, String, required: true, - # validates: { format: { without: /\A[aeiou]/ } } + # **Example: reject inputs that match a pattern** # - # # It's pretty hard to come up with a legitimate use case for `without:` + # ```ruby + # argument :word_that_doesnt_begin_with_a_vowel, String, required: true, + # validates: { format: { without: /\A[aeiou]/ } } # + # # It's pretty hard to come up with a legitimate use case for `without:` + # ``` class FormatValidator < Validator - # @param with [RegExp, nil] - # @param without [Regexp, nil] - # @param message [String] + # **Parameters** + # + # - `with` (`RegExp, nil`) + # - `without` (`Regexp, nil`) + # - `message` (`String`) + # + # :call-seq: + # initialize(RegExp | nil with:, Regexp | nil without:, String message:, **default_options) def initialize( with: nil, without: nil, diff --git a/lib/graphql/schema/validator/inclusion_validator.rb b/lib/graphql/schema/validator/inclusion_validator.rb index 2cf4f3e0a57..08cbd3170c7 100644 --- a/lib/graphql/schema/validator/inclusion_validator.rb +++ b/lib/graphql/schema/validator/inclusion_validator.rb @@ -5,16 +5,24 @@ class Schema class Validator # You can use this to allow certain values for an argument. # - # Usually, a {GraphQL::Schema::Enum} is better for this, because it's self-documenting. + # Usually, a [GraphQL::Schema::Enum](rdoc-ref:GraphQL::Schema::Enum) is better for this, because it's self-documenting. # - # @example only allow certain values for an argument + # **Examples** # - # argument :favorite_prime, Integer, required: true, - # validates: { inclusion: { in: [2, 3, 5, 7, 11, ... ] } } + # **Example: only allow certain values for an argument** # + # ```ruby + # argument :favorite_prime, Integer, required: true, + # validates: { inclusion: { in: [2, 3, 5, 7, 11, ... ] } } + # ``` class InclusionValidator < Validator - # @param message [String] - # @param in [Array] The values to allow + # **Parameters** + # + # - `message` (`String`) + # - `in` (`Array`) — The values to allow + # + # :call-seq: + # initialize(Array in:, String message:, **default_options) def initialize(in:, message: "%{validated} is not included in the list", **default_options) # `in` is a reserved word, so work around that @in_list = binding.local_variable_get(:in) diff --git a/lib/graphql/schema/validator/length_validator.rb b/lib/graphql/schema/validator/length_validator.rb index 69a0b2037ab..9e452acd03f 100644 --- a/lib/graphql/schema/validator/length_validator.rb +++ b/lib/graphql/schema/validator/length_validator.rb @@ -5,23 +5,33 @@ class Schema class Validator # Use this to enforce a `.length` restriction on incoming values. It works for both Strings and Lists. # - # @example Allow no more than 10 IDs + # **Examples** # - # argument :ids, [ID], required: true, validates: { length: { maximum: 10 } } + # **Example: Allow no more than 10 IDs** # - # @example Require three selections + # ```ruby + # argument :ids, [ID], required: true, validates: { length: { maximum: 10 } } + # ``` # - # argument :ice_cream_preferences, [ICE_CREAM_FLAVOR], required: true, validates: { length: { is: 3 } } + # **Example: Require three selections** # + # ```ruby + # argument :ice_cream_preferences, [ICE_CREAM_FLAVOR], required: true, validates: { length: { is: 3 } } + # ``` class LengthValidator < Validator - # @param maximum [Integer] - # @param too_long [String] Used when `maximum` is exceeded or value is greater than `within` - # @param minimum [Integer] - # @param too_short [String] Used with value is less than `minimum` or less than `within` - # @param is [Integer] Exact length requirement - # @param wrong_length [String] Used when value doesn't match `is` - # @param within [Range] An allowed range (becomes `minimum:` and `maximum:` under the hood) - # @param message [String] + # **Parameters** + # + # - `maximum` (`Integer`) + # - `too_long` (`String`) — Used when `maximum` is exceeded or value is greater than `within` + # - `minimum` (`Integer`) + # - `too_short` (`String`) — Used with value is less than `minimum` or less than `within` + # - `is` (`Integer`) — Exact length requirement + # - `wrong_length` (`String`) — Used when value doesn't match `is` + # - `within` (`Range`) — An allowed range (becomes `minimum:` and `maximum:` under the hood) + # - `message` (`String`) + # + # :call-seq: + # initialize(Integer maximum:, String too_long:) def initialize( maximum: nil, too_long: "%{validated} is too long (maximum is %{count})", minimum: nil, too_short: "%{validated} is too short (minimum is %{count})", diff --git a/lib/graphql/schema/validator/numericality_validator.rb b/lib/graphql/schema/validator/numericality_validator.rb index f59b08d8685..11be3eae314 100644 --- a/lib/graphql/schema/validator/numericality_validator.rb +++ b/lib/graphql/schema/validator/numericality_validator.rb @@ -4,29 +4,41 @@ class Schema class Validator # Use this to assert numerical comparisons hold true for inputs. # - # @example Require a number between 0 and 1 + # **Examples** # - # argument :batting_average, Float, required: true, validates: { numericality: { within: 0..1 } } + # **Example: Require a number between 0 and 1** # - # @example Require the number 42 + # ```ruby + # argument :batting_average, Float, required: true, validates: { numericality: { within: 0..1 } } + # ``` # - # argument :the_answer, Integer, required: true, validates: { numericality: { equal_to: 42 } } + # **Example: Require the number 42** # - # @example Require a real number + # ```ruby + # argument :the_answer, Integer, required: true, validates: { numericality: { equal_to: 42 } } + # ``` # - # argument :items_count, Integer, required: true, validates: { numericality: { greater_than_or_equal_to: 0 } } + # **Example: Require a real number** # + # ```ruby + # argument :items_count, Integer, required: true, validates: { numericality: { greater_than_or_equal_to: 0 } } + # ``` class NumericalityValidator < Validator - # @param greater_than [Integer] - # @param greater_than_or_equal_to [Integer] - # @param less_than [Integer] - # @param less_than_or_equal_to [Integer] - # @param equal_to [Integer] - # @param other_than [Integer] - # @param odd [Boolean] - # @param even [Boolean] - # @param within [Range] - # @param message [String] used for all validation failures + # **Parameters** + # + # - `greater_than` (`Integer`) + # - `greater_than_or_equal_to` (`Integer`) + # - `less_than` (`Integer`) + # - `less_than_or_equal_to` (`Integer`) + # - `equal_to` (`Integer`) + # - `other_than` (`Integer`) + # - `odd` (`Boolean`) + # - `even` (`Boolean`) + # - `within` (`Range`) + # - `message` (`String`) — used for all validation failures + # + # :call-seq: + # initialize(Integer greater_than:, Integer greater_than_or_equal_to:, Integer less_than:, Integer less_than_or_equal_to:, Integer equal_to:, Integer other_than:, bool odd:, bool even:, Range within:, String message:, null_message:, **default_options) def initialize( greater_than: nil, greater_than_or_equal_to: nil, less_than: nil, less_than_or_equal_to: nil, diff --git a/lib/graphql/schema/validator/required_validator.rb b/lib/graphql/schema/validator/required_validator.rb index dfeabe80bea..abe3b93bdda 100644 --- a/lib/graphql/schema/validator/required_validator.rb +++ b/lib/graphql/schema/validator/required_validator.rb @@ -8,44 +8,57 @@ class Validator # # (This is for specifying mutually exclusive sets of arguments.) # - # If you use {GraphQL::Schema::Visibility} to hide all the arguments in a `one_of: [..]` set, - # then a developer-facing {GraphQL::Error} will be raised during execution. Pass `allow_all_hidden: true` to + # If you use [GraphQL::Schema::Visibility](rdoc-ref:GraphQL::Schema::Visibility) to hide all the arguments in a `one_of: [..]` set, + # then a developer-facing [GraphQL::Error](rdoc-ref:GraphQL::Error) will be raised during execution. Pass `allow_all_hidden: true` to # skip validation in this case instead. # # This validator also implements `argument ... required: :nullable`. If an argument has `required: :nullable` - # but it's hidden with {GraphQL::Schema::Visibility}, then this validator doesn't run. + # but it's hidden with [GraphQL::Schema::Visibility](rdoc-ref:GraphQL::Schema::Visibility), then this validator doesn't run. # - # @example Require exactly one of these arguments + # **Examples** # - # field :update_amount, IngredientAmount, null: false do - # argument :ingredient_id, ID, required: true - # argument :cups, Integer, required: false - # argument :tablespoons, Integer, required: false - # argument :teaspoons, Integer, required: false - # validates required: { one_of: [:cups, :tablespoons, :teaspoons] } - # end + # **Example: Require exactly one of these arguments** # - # @example Require one of these _sets_ of arguments + # ```ruby + # field :update_amount, IngredientAmount, null: false do + # argument :ingredient_id, ID, required: true + # argument :cups, Integer, required: false + # argument :tablespoons, Integer, required: false + # argument :teaspoons, Integer, required: false + # validates required: { one_of: [:cups, :tablespoons, :teaspoons] } + # end + # ``` # - # field :find_object, Node, null: true do - # argument :node_id, ID, required: false - # argument :object_type, String, required: false - # argument :object_id, Integer, required: false - # # either a global `node_id` or an `object_type`/`object_id` pair is required: - # validates required: { one_of: [:node_id, [:object_type, :object_id]] } - # end + # **Example: Require one of these _sets_ of arguments** # - # @example require _some_ value for an argument, even if it's null - # field :update_settings, AccountSettings do - # # `required: :nullable` means this argument must be given, but may be `null` - # argument :age, Integer, required: :nullable - # end + # ```ruby + # field :find_object, Node, null: true do + # argument :node_id, ID, required: false + # argument :object_type, String, required: false + # argument :object_id, Integer, required: false + # # either a global `node_id` or an `object_type`/`object_id` pair is required: + # validates required: { one_of: [:node_id, [:object_type, :object_id]] } + # end + # ``` # + # **Example: require _some_ value for an argument, even if it's null** + # + # ```ruby + # field :update_settings, AccountSettings do + # # `required: :nullable` means this argument must be given, but may be `null` + # argument :age, Integer, required: :nullable + # end + # ``` class RequiredValidator < Validator - # @param one_of [Array] A list of arguments, exactly one of which is required for this field - # @param argument [Symbol] An argument that is required for this field - # @param allow_all_hidden [Boolean] If `true`, then this validator won't run if all the `one_of: ...` arguments have been hidden - # @param message [String] + # **Parameters** + # + # - `one_of` (`Array`) — A list of arguments, exactly one of which is required for this field + # - `argument` (`Symbol`) — An argument that is required for this field + # - `allow_all_hidden` (`Boolean`) — If `true`, then this validator won't run if all the `one_of: ...` arguments have been hidden + # - `message` (`String`) + # + # :call-seq: + # initialize(Array[Symbol] one_of:, Symbol argument:, bool allow_all_hidden:, String message:, **default_options) def initialize(one_of: nil, argument: nil, allow_all_hidden: nil, message: nil, **default_options) @one_of = if one_of one_of diff --git a/lib/graphql/schema/visibility.rb b/lib/graphql/schema/visibility.rb index cae29642833..7acf6d6f957 100644 --- a/lib/graphql/schema/visibility.rb +++ b/lib/graphql/schema/visibility.rb @@ -14,10 +14,15 @@ def initialize(config_message, config_str) super(message) end end - # @param schema [Class] - # @param profiles [Hash Hash>] A hash of `name => context` pairs for preloading visibility profiles - # @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?` and `Rails.env.staging?`) - # @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results + # **Parameters** + # + # - `schema` (`Class`) + # - `profiles` (`Hash Hash>`) — A hash of `name => context` pairs for preloading visibility profiles + # - `preload` (`Boolean`) — if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?` and `Rails.env.staging?`) + # - `migration_errors` (`Boolean`) — if `true`, raise an error when `Visibility` and `Warden` return different results + # + # :call-seq: + # use(Class[GraphQL::Schema] schema, dynamic:, Hash[Symbol, Hash] profiles:, bool preload:) def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails.env) ? (Rails.env.production? || Rails.env.staging? || nil) : false), migration_errors: false) profiles&.each { |name, ctx| ctx[:visibility_profile] = name @@ -108,35 +113,32 @@ def preload end end - # @api private - def query_configured(query_type) + def query_configured(query_type) # :nodoc: require_if_preloaded("a query type was", "query(...)") end - # @api private - def mutation_configured(mutation_type) + def mutation_configured(mutation_type) # :nodoc: require_if_preloaded("a mutation type was", "mutation(...)") end - # @api private - def subscription_configured(subscription_type) + def subscription_configured(subscription_type) # :nodoc: require_if_preloaded("a mutation type was", "subscription(...)") end - # @api private - def orphan_types_configured(orphan_types) + def orphan_types_configured(orphan_types) # :nodoc: require_if_preloaded("orphan types were", "orphan_types(...)") end - # @api private - def introspection_system_configured(introspection_system) + def introspection_system_configured(introspection_system) # :nodoc: require_if_preloaded("custom introspection was", "introspection(...)") end # Make another Visibility for `schema` based on this one - # @return [Visibility] - # @api private - def dup_for(other_schema) + # + # **Returns** + # + # - `Visibility` + def dup_for(other_schema) # :nodoc: self.class.new( other_schema, dynamic: @dynamic, @@ -179,8 +181,7 @@ def profile_for(context) attr_reader :top_level - # @api private - attr_reader :unfiltered_interface_type_memberships + attr_reader :unfiltered_interface_type_memberships # :nodoc: def top_level_profile(refresh: false) if refresh diff --git a/lib/graphql/schema/visibility/migration.rb b/lib/graphql/schema/visibility/migration.rb index fa545f130e6..077c259b49b 100644 --- a/lib/graphql/schema/visibility/migration.rb +++ b/lib/graphql/schema/visibility/migration.rb @@ -2,7 +2,7 @@ module GraphQL class Schema class Visibility - # You can use this to see how {GraphQL::Schema::Warden} and {GraphQL::Schema::Visibility::Profile} + # You can use this to see how [GraphQL::Schema::Warden](rdoc-ref:GraphQL::Schema::Warden) and [GraphQL::Schema::Visibility::Profile](rdoc-ref:GraphQL::Schema::Visibility::Profile) # handle `.visible?` differently in your schema. # # It runs the same method on both implementations and raises an error when the results diverge. @@ -15,17 +15,20 @@ class Visibility # This plugin adds two keys to `context` when running: # # - `visibility_migration_running: true` - # - For the {Schema::Warden} which it instantiates, it adds `visibility_migration_warden_running: true`. + # - For the [Schema::Warden](rdoc-ref:Schema::Warden) which it instantiates, it adds `visibility_migration_warden_running: true`. # # Use those keys to modify your `visible?` behavior as needed. # # Also, in a pinch, you can set `skip_visibility_migration_error: true` in context to turn off this behavior per-query. - # (In that case, it uses {Profile} directly.) + # (In that case, it uses [Profile](rdoc-ref:Profile) directly.) # - # @example Adding this plugin + # **Examples** # - # use GraphQL::Schema::Visibility, migration_errors: true + # **Example: Adding this plugin** # + # ```ruby + # use GraphQL::Schema::Visibility, migration_errors: true + # ``` class Migration < GraphQL::Schema::Visibility::Profile class RuntimeTypesMismatchError < GraphQL::Error def initialize(method_called, warden_result, profile_result, method_args) diff --git a/lib/graphql/schema/visibility/profile.rb b/lib/graphql/schema/visibility/profile.rb index 9fab2198ca3..d2e9c6eae1b 100644 --- a/lib/graphql/schema/visibility/profile.rb +++ b/lib/graphql/schema/visibility/profile.rb @@ -13,7 +13,12 @@ class Visibility # - It checks `.visible?` on root introspection types # - It can be used to cache profiles by name for re-use across queries class Profile - # @return [Schema::Visibility::Profile] + # **Returns** + # + # - `Schema::Visibility::Profile` + # + # :call-seq: + # from_context(ctx, schema) -> Schema::Visibility::Profile def self.from_context(ctx, schema) if ctx.respond_to?(:types) && (types = ctx.types).is_a?(self) types @@ -28,7 +33,12 @@ def self.null_profile(context:, schema:) profile end - # @return [Symbol, nil] + # **Returns** + # + # - `Symbol, nil` + # + # :call-seq: + # name -> Symbol | nil attr_reader :name def freeze diff --git a/lib/graphql/schema/warden.rb b/lib/graphql/schema/warden.rb index d74956738f2..27685c6e9ec 100644 --- a/lib/graphql/schema/warden.rb +++ b/lib/graphql/schema/warden.rb @@ -4,14 +4,13 @@ module GraphQL class Schema - # Restrict access to a {GraphQL::Schema} with a user-defined `visible?` implementations. + # Restrict access to a [GraphQL::Schema](rdoc-ref:GraphQL::Schema) with a user-defined `visible?` implementations. # # 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. # - # @api private - class Warden + class Warden # :nodoc: def self.from_context(context) context.warden || PassThruWarden rescue NoMethodError @@ -30,12 +29,20 @@ def self.use(schema) # no-op end - # @param visibility_method [Symbol] a Warden method to call for this entry - # @param entry [Object, Array] One or more definitions for a given name in a GraphQL Schema - # @param context [GraphQL::Query::Context] - # @param warden [Warden] - # @return [Object] `entry` or one of `entry`'s items if exactly one of them is visible for this context - # @return [nil] If neither `entry` nor any of `entry`'s items are visible for this context + # **Parameters** + # + # - `visibility_method` (`Symbol`) — a Warden method to call for this entry + # - `entry` (`Object, Array`) — One or more definitions for a given name in a GraphQL Schema + # - `context` (`GraphQL::Query::Context`) + # - `warden` (`Warden`) + # + # **Returns** + # + # - `Object` — `entry` or one of `entry`'s items if exactly one of them is visible for this context + # - `nil` — If neither `entry` nor any of `entry`'s items are visible for this context + # + # :call-seq: + # visible_entry?(Symbol visibility_method, Object | Array[Object] entry, GraphQL::Query::Context context, Warden warden) -> Object | nil def self.visible_entry?(visibility_method, entry, context, warden = Warden.from_context(context)) if entry.is_a?(Array) visible_item = nil @@ -195,8 +202,13 @@ def visible_enum_value?(enum_value, ctx = nil) end end - # @param context [GraphQL::Query::Context] - # @param schema [GraphQL::Schema] + # **Parameters** + # + # - `context` (`GraphQL::Query::Context`) + # - `schema` (`GraphQL::Schema`) + # + # :call-seq: + # initialize(GraphQL::Query::Context context:, GraphQL::Schema schema:) def initialize(context:, schema:) @schema = schema # Cache these to avoid repeated hits to the inheritance chain when one isn't present @@ -217,7 +229,12 @@ def initialize(context:, schema:) attr_writer :skip_warning - # @return [Hash] Visible types in the schema + # **Returns** + # + # - `Hash` — Visible types in the schema + # + # :call-seq: + # types() -> Hash[String, GraphQL::BaseType] def types @types ||= begin vis_types = {} @@ -230,7 +247,12 @@ def types end end - # @return [Boolean] True if this type is used for `loads:` but not in the schema otherwise and not _explicitly_ hidden. + # **Returns** + # + # - `Boolean` — True if this type is used for `loads:` but not in the schema otherwise and not _explicitly_ hidden. + # + # :call-seq: + # loadable?(type, _ctx) -> bool def loadable?(type, _ctx) visible_type?(type) && !referenced?(type) && @@ -250,7 +272,12 @@ def loadable_possible_types(abstract_type, _ctx) @loadable_possible_types[abstract_type] end - # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`) + # **Returns** + # + # - `GraphQL::BaseType, nil` — The type named `type_name`, if it exists (else `nil`) + # + # :call-seq: + # get_type(type_name) -> GraphQL::BaseType | nil def get_type(type_name) @visible_types ||= read_through do |name| type_defn = @schema.get_type(name, @context, false) @@ -264,18 +291,33 @@ def get_type(type_name) @visible_types[type_name] end - # @return [Array] Visible and reachable types in the schema + # **Returns** + # + # - `Array` — Visible and reachable types in the schema + # + # :call-seq: + # reachable_types() -> Array[GraphQL::BaseType] def reachable_types @reachable_types ||= reachable_type_set.to_a end - # @return Boolean True if the type is visible and reachable in the schema + # **Returns** + # + # - `Object` — Boolean True if the type is visible and reachable in the schema + # + # :call-seq: + # reachable_type?(type_name) -> Object def reachable_type?(type_name) type = get_type(type_name) # rubocop:disable Development/ContextIsPassedCop -- `self` is query-aware type && reachable_type_set.include?(type) end - # @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`, if it exists + # **Returns** + # + # - `GraphQL::Field, nil` — The field named `field_name` on `parent_type`, if it exists + # + # :call-seq: + # get_field(parent_type, field_name) -> GraphQL::Field | nil def get_field(parent_type, field_name) @visible_parent_fields ||= read_through do |type| read_through do |f_name| @@ -291,13 +333,23 @@ def get_field(parent_type, field_name) @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 + # **Returns** + # + # - `GraphQL::Argument, nil` — The argument named `argument_name` on `parent_type`, if it exists and is visible + # + # :call-seq: + # get_argument(parent_type, argument_name) -> GraphQL::Argument | nil def get_argument(parent_type, argument_name) argument = parent_type.get_argument(argument_name, @context) return argument if argument && visible_argument?(argument, @context) end - # @return [Array] The types which may be member of `type_defn` + # **Returns** + # + # - `Array` — The types which may be member of `type_defn` + # + # :call-seq: + # possible_types(type_defn) -> Array[GraphQL::BaseType] def possible_types(type_defn) @visible_possible_types ||= read_through { |type_defn| pt = @schema.possible_types(type_defn, @context, false) @@ -306,15 +358,31 @@ def possible_types(type_defn) @visible_possible_types[type_defn] end - # @param type_defn [GraphQL::ObjectType, GraphQL::InterfaceType] - # @return [Array] Fields on `type_defn` + # **Parameters** + # + # - `type_defn` (`GraphQL::ObjectType, GraphQL::InterfaceType`) + # + # **Returns** + # + # - `Array` — Fields on `type_defn` + # + # :call-seq: + # fields(GraphQL::ObjectType | GraphQL::InterfaceType type_defn) -> Array[GraphQL::Field] def fields(type_defn) @visible_fields ||= read_through { |t| @schema.get_fields(t, @context).values } @visible_fields[type_defn] end - # @param argument_owner [GraphQL::Field, GraphQL::InputObjectType] - # @return [Array] Visible arguments on `argument_owner` + # **Parameters** + # + # - `argument_owner` (`GraphQL::Field, GraphQL::InputObjectType`) + # + # **Returns** + # + # - `Array` — Visible arguments on `argument_owner` + # + # :call-seq: + # arguments(GraphQL::Field | GraphQL::InputObjectType argument_owner, ctx) -> Array[GraphQL::Argument] def arguments(argument_owner, ctx = nil) @visible_arguments ||= read_through { |o| args = o.arguments(@context) @@ -329,7 +397,12 @@ def arguments(argument_owner, ctx = nil) @visible_arguments[argument_owner] end - # @return [Array] Visible members of `enum_defn` + # **Returns** + # + # - `Array` — Visible members of `enum_defn` + # + # :call-seq: + # enum_values(enum_defn) -> Array[GraphQL::EnumType::EnumValue] def enum_values(enum_defn) @visible_enum_arrays ||= read_through { |e| values = e.enum_values(@context) @@ -346,7 +419,12 @@ def visible_enum_value?(enum_value, _ctx = nil) @visible_enum_values[enum_value] end - # @return [Array] Visible interfaces implemented by `obj_type` + # **Returns** + # + # - `Array` — Visible interfaces implemented by `obj_type` + # + # :call-seq: + # interfaces(obj_type) -> Array[GraphQL::InterfaceType] def interfaces(obj_type) @visible_interfaces ||= read_through { |t| ints = t.interfaces(@context) @@ -371,7 +449,12 @@ def root_type_for_operation(op_name) end end - # @param owner [Class, Module] If provided, confirm that field has the given owner. + # **Parameters** + # + # - `owner` (`Class, Module`) — If provided, confirm that field has the given owner. + # + # :call-seq: + # visible_field?(field_defn, _ctx, Class | Module owner) def visible_field?(field_defn, _ctx = nil, owner = field_defn.owner) # This field is visible in its own right visible?(field_defn) && diff --git a/lib/graphql/schema/wrapper.rb b/lib/graphql/schema/wrapper.rb index 8fd49e19619..5da78db06a7 100644 --- a/lib/graphql/schema/wrapper.rb +++ b/lib/graphql/schema/wrapper.rb @@ -5,7 +5,12 @@ class Schema class Wrapper 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. + # **Returns** + # + # - `Class, Module` — The inner type of this wrapping type, the type of which one or more objects may be present. + # + # :call-seq: + # of_type -> Class | Module attr_reader :of_type def initialize(of_type) diff --git a/lib/graphql/static_validation/base_visitor.rb b/lib/graphql/static_validation/base_visitor.rb index c133cc766d1..2466a566e0d 100644 --- a/lib/graphql/static_validation/base_visitor.rb +++ b/lib/graphql/static_validation/base_visitor.rb @@ -21,15 +21,29 @@ def initialize(document, context) attr_reader :context - # @return [Array] The nesting of the current position in the AST + # **Returns** + # + # - `Array` — The nesting of the current position in the AST + # + # :call-seq: + # path() -> Array[String] def path @path[0, @path_depth] 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] - # @return [Class] A class for validating `rules` during visitation + # + # **Parameters** + # + # - `rules` (`Array`) + # + # **Returns** + # + # - `Class` — A class for validating `rules` during visitation + # + # :call-seq: + # including_rules(Array[Module | Class] rules) -> Class def self.including_rules(rules) if rules.empty? # It's not doing _anything?!?_ @@ -180,27 +194,52 @@ def on_input_object(node, parent) end end - # @return [GraphQL::BaseType] The current object type + # **Returns** + # + # - `GraphQL::BaseType` — The current object type + # + # :call-seq: + # type_definition() -> GraphQL::BaseType def type_definition @current_object_type end - # @return [GraphQL::BaseType] The type which the current type came from + # **Returns** + # + # - `GraphQL::BaseType` — The type which the current type came from + # + # :call-seq: + # parent_type_definition() -> GraphQL::BaseType def parent_type_definition @parent_object_type end - # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one + # **Returns** + # + # - `GraphQL::Field, nil` — The most-recently-entered GraphQL::Field, if currently inside one + # + # :call-seq: + # field_definition() -> GraphQL::Field | nil def field_definition @current_field_definition end - # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one + # **Returns** + # + # - `GraphQL::Directive, nil` — The most-recently-entered GraphQL::Directive, if currently inside one + # + # :call-seq: + # directive_definition() -> GraphQL::Directive | nil def directive_definition @current_directive_definition end - # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one + # **Returns** + # + # - `GraphQL::Argument, nil` — The most-recently-entered GraphQL::Argument, if currently inside one + # + # :call-seq: + # argument_definition() -> GraphQL::Argument | nil def argument_definition # Return the parent argument definition (not the current one). @parent_argument_definition diff --git a/lib/graphql/static_validation/definition_dependencies.rb b/lib/graphql/static_validation/definition_dependencies.rb index 2ef2e402b24..2d6232fadd4 100644 --- a/lib/graphql/static_validation/definition_dependencies.rb +++ b/lib/graphql/static_validation/definition_dependencies.rb @@ -65,7 +65,13 @@ def on_fragment_spread(node, parent) end # A map of operation definitions to an array of that operation's dependencies - # @return [DependencyMap] + # + # **Returns** + # + # - `DependencyMap` + # + # :call-seq: + # dependency_map(&block) -> DependencyMap def dependency_map(&block) @dependency_map ||= resolve_dependencies(&block) end @@ -73,13 +79,28 @@ def dependency_map(&block) # Map definition AST nodes to the definition AST nodes they depend on. # Expose circular dependencies. class DependencyMap - # @return [Array] + # **Returns** + # + # - `Array` + # + # :call-seq: + # cyclical_definitions -> Array[GraphQL::Language::Nodes::FragmentDefinition] attr_reader :cyclical_definitions - # @return [Hash>] + # **Returns** + # + # - `Hash>` + # + # :call-seq: + # unmet_dependencies -> Hash[Node, Array[GraphQL::Language::Nodes::FragmentSpread]] attr_reader :unmet_dependencies - # @return [Array] + # **Returns** + # + # - `Array` + # + # :call-seq: + # unused_dependencies -> Array[GraphQL::Language::Nodes::FragmentDefinition] attr_reader :unused_dependencies def initialize @@ -89,7 +110,12 @@ def initialize @unused_dependencies = [] end - # @return [Array] dependencies for `definition_node` + # **Returns** + # + # - `Array` — dependencies for `definition_node` + # + # :call-seq: + # [](definition_node) -> Array[GraphQL::Language::Nodes::AbstractNode] def [](definition_node) @dependencies[definition_node] end @@ -190,9 +216,9 @@ def resolve_dependencies end end - # Anything left in @immediate_dependencies is cyclical + # 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 + # `@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) diff --git a/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb b/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb index af2343753da..379489a2c1f 100644 --- a/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +++ b/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb @@ -113,7 +113,12 @@ def wrap_var_type_with_depth_of_arg(var_type, arg_node) new_var_type end - # @return [Integer] Returns the max depth of `array`, or `0` if it isn't an array at all + # **Returns** + # + # - `Integer` — Returns the max depth of `array`, or `0` if it isn't an array at all + # + # :call-seq: + # depth_of_array(array) -> Integer def depth_of_array(array) case array when Array diff --git a/lib/graphql/static_validation/validator.rb b/lib/graphql/static_validation/validator.rb index 2cd83a4dab6..3937695e3d1 100644 --- a/lib/graphql/static_validation/validator.rb +++ b/lib/graphql/static_validation/validator.rb @@ -3,29 +3,47 @@ module GraphQL module StaticValidation - # Initialized with a {GraphQL::Schema}, then it can validate {GraphQL::Language::Nodes::Documents}s based on that schema. + # Initialized with a [GraphQL::Schema](rdoc-ref:GraphQL::Schema), then it can validate [GraphQL::Language::Nodes::Document](rdoc-ref:GraphQL::Language::Nodes::Document) nodes based on that schema. # - # By default, it's used by {GraphQL::Query} + # By default, it's used by [GraphQL::Query](rdoc-ref: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] + # **Examples** # + # **Example: Validate a query** + # + # ```ruby + # 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 + # **Parameters** + # + # - `schema` (`GraphQL::Schema`) + # - `rules` (`Array<#validate(context)>`) — a list of rules to use when validating + # + # :call-seq: + # initialize(GraphQL::Schema schema:, Array[#validate(context)] rules:) 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] + # + # **Parameters** + # + # - `query` (`GraphQL::Query`) + # - `validate` (`Boolean`) + # - `timeout` (`Float`) — Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds. + # - `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. + # + # **Returns** + # + # - `Array` + # + # :call-seq: + # validate(GraphQL::Query query, bool validate:, Float timeout:, Integer max_errors:) -> Array[Hash] def validate(query, validate: true, timeout: nil, max_errors: nil) errors = nil query.current_trace.begin_validate(query, validate) @@ -70,8 +88,14 @@ def validate(query, validate: true, timeout: nil, max_errors: nil) end # Invoked when static validation times out. - # @param query [GraphQL::Query] - # @param context [GraphQL::StaticValidation::ValidationContext] + # + # **Parameters** + # + # - `query` (`GraphQL::Query`) + # - `context` (`GraphQL::StaticValidation::ValidationContext`) + # + # :call-seq: + # handle_timeout(GraphQL::Query query, GraphQL::StaticValidation::ValidationContext context) def handle_timeout(query, context) context.errors << GraphQL::StaticValidation::ValidationTimeoutError.new( "Timeout on validation of query" diff --git a/lib/graphql/subscriptions.rb b/lib/graphql/subscriptions.rb index bc6a1a28390..e3ffc12f4ca 100644 --- a/lib/graphql/subscriptions.rb +++ b/lib/graphql/subscriptions.rb @@ -21,7 +21,7 @@ class InvalidTriggerError < GraphQL::Error class SubscriptionScopeMissingError < GraphQL::Error end - # @see {Subscriptions#initialize} for options, concrete implementations may add options. + # See [GraphQL::Subscriptions](rdoc-ref:GraphQL::Subscriptions) for the base options; concrete implementations may add options. def self.use(defn, options = {}) schema = defn.is_a?(Class) ? defn : defn.target @@ -35,8 +35,13 @@ def self.use(defn, options = {}) nil end - # @param schema [Class] the GraphQL schema this manager belongs to - # @param validate_update [Boolean] If false, then validation is skipped when executing updates + # **Parameters** + # + # - `schema` (`Class`) — the GraphQL schema this manager belongs to + # - `validate_update` (`Boolean`) — If false, then validation is skipped when executing updates + # + # :call-seq: + # initialize(Class schema:, bool validate_update:, broadcast:, default_broadcastable:, **rest) def initialize(schema:, validate_update: true, broadcast: false, default_broadcastable: false, **rest) if broadcast schema.query_analyzer(Subscriptions::BroadcastAnalyzer) @@ -46,17 +51,31 @@ def initialize(schema:, validate_update: true, broadcast: false, default_broadca @validate_update = validate_update end - # @return [Boolean] Used when fields don't have `broadcastable:` explicitly set + # **Returns** + # + # - `Boolean` — Used when fields don't have `broadcastable:` explicitly set + # + # :call-seq: + # default_broadcastable -> bool 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] - # @param context [Hash] - # @return [void] + # + # **Parameters** + # + # - `event_name` (`String`) + # - `args` (`Hash Object>`) — Arguments passed to the subscription resolver + # - `object` (`Object`) + # - `scope` (`Symbol, String`) + # - `context` (`Hash`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # trigger(String event_name, Hash[String | Symbol, Object] args, Object object, Symbol | String scope:, Hash context:) -> void def trigger(event_name, args, object, scope: nil, context: {}) # Make something as context-like as possible, even though there isn't a current query: dummy_query = @schema.query_class.new(@schema, "{ __typename }", validate: false, context: context) @@ -97,10 +116,18 @@ def trigger(event_name, args, object, scope: nil, context: {}) # # 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] + # **Parameters** + # + # - `subscription_id` (`String`) + # - `event` (`GraphQL::Subscriptions::Event`) — The event which was triggered + # - `object` (`Object`) — The value for the subscription field + # + # **Returns** + # + # - `GraphQL::Query::Result` + # + # :call-seq: + # execute_update(String subscription_id, GraphQL::Subscriptions::Event event, Object object) -> GraphQL::Query::Result def execute_update(subscription_id, event, object) # Lookup the saved data for this subscription query_data = read_subscription(subscription_id) @@ -146,15 +173,26 @@ def execute_update(subscription_id, event, object) # Define this method to customize whether to validate # this subscription when executing an update. # - # @return [Boolean] defaults to `true`, or false if `validate: false` is provided. + # **Returns** + # + # - `Boolean` — defaults to `true`, or false if `validate: false` is provided. + # + # :call-seq: + # validate_update?(query:, context:, root_value:, subscription_topic:, operation_name:, variables:) -> bool def validate_update?(query:, context:, root_value:, subscription_topic:, operation_name:, variables:) @validate_update end # Run the update query for this subscription and deliver it - # @see {#execute_update} - # @see {#deliver} - # @return [void] + # See [execute_update](rdoc-ref:#execute_update) + # See [deliver](rdoc-ref:#deliver) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # execute(subscription_id, event, object) -> void def execute(subscription_id, event, object) res = execute_update(subscription_id, event, object) if !res.nil? @@ -171,48 +209,98 @@ def execute(subscription_id, event, object) # Event `event` occurred on `object`, # Update all subscribers. - # @param event [Subscriptions::Event] - # @param object [Object] - # @return [void] + # + # **Parameters** + # + # - `event` (`Subscriptions::Event`) + # - `object` (`Object`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # execute_all(Subscriptions::Event event, Object object) -> void def execute_all(event, object) 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 + # + # **Parameters** + # + # - `subscription_id` (`String`) + # + # **Returns** + # + # - `Hash` — Containing required keys + # + # :call-seq: + # read_subscription(String subscription_id) -> Hash 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] + # + # **Parameters** + # + # - `subscription_id` (`String`) + # - `result` (`Hash`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # deliver(String subscription_id, Hash result) -> 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] + # + # **Parameters** + # + # - `query` (`GraphQL::Query`) + # - `events` (`Array`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # write_subscription(GraphQL::Query query, Array[GraphQL::Subscriptions::Event] events) -> 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. + # + # **Parameters** + # + # - `subscription_id` (`String`) + # + # **Returns** + # + # - `Object` — void. + # + # :call-seq: + # delete_subscription(String subscription_id) -> Object def delete_subscription(subscription_id) raise GraphQL::RequiredImplementationMissingError end - # @return [String] A new unique identifier for a subscription + # **Returns** + # + # - `String` — A new unique identifier for a subscription + # + # :call-seq: + # build_id() -> String def build_id SecureRandom.uuid end @@ -223,13 +311,26 @@ def build_id # 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] + # **Parameters** + # + # - `event_or_arg_name` (`String, Symbol`) + # + # **Returns** + # + # - `String` + # + # :call-seq: + # normalize_name(String | Symbol event_or_arg_name) -> 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 + # **Returns** + # + # - `Boolean` — if true, then a query like this one would be broadcasted + # + # :call-seq: + # broadcastable?(query_str, **query_options) -> bool def broadcastable?(query_str, **query_options) query = @schema.query_class.new(@schema, query_str, **query_options) if !query.valid? @@ -240,8 +341,17 @@ def broadcastable?(query_str, **query_options) end # Called during execution when a new `subscription ...` operation is received - # @param query [GraphQL::Query] - # @return [void] + # + # **Parameters** + # + # - `query` (`GraphQL::Query`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # initialize_subscriptions(GraphQL::Query query) -> void def initialize_subscriptions(query) subs_namespace = query.context.namespace(:subscriptions) subs_namespace[:events] = [] @@ -250,8 +360,17 @@ def initialize_subscriptions(query) end # Called during execution when a subscription operation has finished - # @param query [GraphQL::Query] - # @return [void] + # + # **Parameters** + # + # - `query` (`GraphQL::Query`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # finish_subscriptions(GraphQL::Query query) -> void def finish_subscriptions(query) if (events = query.context.namespace(:subscriptions)[:events]) && !events.empty? write_subscription(query, events) @@ -278,10 +397,19 @@ def finalize_graphql_result(query, result_data, result_key) # 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 + # - if needed, camelize the string (using [normalize name](rdoc-ref:#normalize_name)) + # + # **Parameters** + # + # - `arg_owner` (`GraphQL::Field, GraphQL::BaseType`) + # - `args` (`Hash, Array, Any`) — some GraphQL input value to coerce as `arg_owner` + # + # **Returns** + # + # - `Any` — normalized arguments value + # + # :call-seq: + # normalize_arguments(event_name, GraphQL::Field | GraphQL::BaseType arg_owner, Hash | Array | Any args, context) -> Any def normalize_arguments(event_name, arg_owner, args, context) case arg_owner when GraphQL::Schema::Field, Class diff --git a/lib/graphql/subscriptions/action_cable_subscriptions.rb b/lib/graphql/subscriptions/action_cable_subscriptions.rb index bc3dc903ee4..74f9191a5d2 100644 --- a/lib/graphql/subscriptions/action_cable_subscriptions.rb +++ b/lib/graphql/subscriptions/action_cable_subscriptions.rb @@ -7,87 +7,100 @@ class Subscriptions # 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?}) + # - Take care to reload context when re-delivering the subscription. (see [Query#subscription_update?](rdoc-ref: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 + # See [GraphQL::Testing::MockActionCable](rdoc-ref:GraphQL::Testing::MockActionCable) for test helpers # - # @example Implementing a channel for GraphQL Subscriptions - # class GraphqlChannel < ApplicationCable::Channel - # def subscribed - # @subscription_ids = [] - # end + # **Examples** # - # 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, - # } + # **Example: Adding ActionCableSubscriptions to your schema** # - # result = MySchema.execute( - # query, - # context: context, - # variables: variables, - # operation_name: operation_name - # ) + # ```ruby + # class MySchema < GraphQL::Schema + # # ... + # use GraphQL::Subscriptions::ActionCableSubscriptions + # end + # ``` # - # payload = { - # result: result.to_h, - # more: result.subscription?, - # } + # **Example: Implementing a channel for GraphQL Subscriptions** # - # # Track the subscription here so we can remove it - # # on unsubscribe. - # if result.context[:subscription_id] - # @subscription_ids << result.context[:subscription_id] - # end + # ```ruby + # class GraphqlChannel < ApplicationCable::Channel + # def subscribed + # @subscription_ids = [] + # end # - # transmit(payload) - # 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, + # context: context, + # variables: variables, + # operation_name: operation_name + # ) # - # def unsubscribed - # @subscription_ids.each { |sid| - # MySchema.subscriptions.delete_subscription(sid) - # } + # 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 # - # private + # transmit(payload) + # end # - # 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 - # {} + # 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 - # raise ArgumentError, "Unexpected parameter: #{ambiguous_param}" + # {} # end + # when Hash, ActionController::Parameters + # ambiguous_param + # when nil + # {} + # else + # raise ArgumentError, "Unexpected parameter: #{ambiguous_param}" # end - # end - # - # @see GraphQL::Testing::MockActionCable for test helpers + # 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: '') + # **Parameters** + # + # - `serializer` (`<#dump(obj), #load(string)>`) — Used for serializing messages before handing them to `.broadcast(msg)` + # - `namespace` (`string`) — Used to namespace events and subscriptions (default: '') + # + # :call-seq: + # initialize(#dump(obj), #load(string) serializer:, string namespace:, action_cable:, action_cable_coder:, **rest) 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 @@ -194,8 +207,14 @@ def setup_stream(channel, initial_event) # This is called to turn an ActionCable-broadcasted string (JSON) # into a query-ready application object. - # @param message [String] n ActionCable-broadcasted string (JSON) - # @param context [GraphQL::Query::Context] the context of the first event for a given subscription fingerprint + # + # **Parameters** + # + # - `message` (`String`) — n ActionCable-broadcasted string (JSON) + # - `context` (`GraphQL::Query::Context`) — the context of the first event for a given subscription fingerprint + # + # :call-seq: + # load_action_cable_message(String message, GraphQL::Query::Context context) def load_action_cable_message(message, context) if @serialize_with_context @serializer.load(message, context) diff --git a/lib/graphql/subscriptions/broadcast_analyzer.rb b/lib/graphql/subscriptions/broadcast_analyzer.rb index 3a6a43a8f92..ad21a8160e5 100644 --- a/lib/graphql/subscriptions/broadcast_analyzer.rb +++ b/lib/graphql/subscriptions/broadcast_analyzer.rb @@ -7,9 +7,8 @@ class Subscriptions # - 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::Analyzer + # See [Subscriptions#broadcastable?](rdoc-ref:Subscriptions#broadcastable?) for a public API + class BroadcastAnalyzer < GraphQL::Analysis::Analyzer # :nodoc: def initialize(subject) super @default_broadcastable = subject.schema.subscriptions.default_broadcastable @@ -45,7 +44,13 @@ def on_enter_field(node, parent, visitor) # Assign the result to context. # (This method is allowed to return an error, but we don't need to) - # @return [void] + # + # **Returns** + # + # - `void` + # + # :call-seq: + # result() -> void def result query.context.namespace(:subscriptions)[:subscription_broadcastable] = @subscription_broadcastable nil diff --git a/lib/graphql/subscriptions/event.rb b/lib/graphql/subscriptions/event.rb index d2d8ce7abb0..c93aea06a29 100644 --- a/lib/graphql/subscriptions/event.rb +++ b/lib/graphql/subscriptions/event.rb @@ -6,16 +6,36 @@ class Subscriptions # - Triggered by `MySchema.subscriber.trigger(name, arguments, obj)` # class Event - # @return [String] Corresponds to the Subscription root field name + # **Returns** + # + # - `String` — Corresponds to the Subscription root field name + # + # :call-seq: + # name -> String attr_reader :name - # @return [GraphQL::Execution::Interpreter::Arguments] + # **Returns** + # + # - `GraphQL::Execution::Interpreter::Arguments` + # + # :call-seq: + # arguments -> GraphQL::Execution::Interpreter::Arguments attr_reader :arguments - # @return [GraphQL::Query::Context] + # **Returns** + # + # - `GraphQL::Query::Context` + # + # :call-seq: + # context -> GraphQL::Query::Context attr_reader :context - # @return [String] An opaque string which identifies this event, derived from `name` and `arguments` + # **Returns** + # + # - `String` — An opaque string which identifies this event, derived from `name` and `arguments` + # + # :call-seq: + # topic -> String attr_reader :topic def initialize(name:, arguments:, field: nil, context: nil, scope: nil) @@ -36,7 +56,12 @@ def initialize(name:, arguments:, field: nil, context: nil, scope: nil) @topic = self.class.serialize(name, arguments, field, scope: scope_val, context: context) end - # @return [String] an identifier for this unit of subscription + # **Returns** + # + # - `String` — an identifier for this unit of subscription + # + # :call-seq: + # serialize(_name, arguments, field, scope:, context:) -> String def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext.instance) subscription = field.resolver || GraphQL::Schema::Subscription arguments = arguments_without_field_extras(field: field, arguments: arguments) @@ -44,7 +69,12 @@ def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::Nul subscription.topic_for(arguments: normalized_args, field: field, scope: scope) end - # @return [String] a logical identifier for this event. (Stable when the query is broadcastable.) + # **Returns** + # + # - `String` — a logical identifier for this event. (Stable when the query is broadcastable.) + # + # :call-seq: + # fingerprint() -> String def fingerprint @fingerprint ||= begin # When this query has been flagged as broadcastable, diff --git a/lib/graphql/subscriptions/serialize.rb b/lib/graphql/subscriptions/serialize.rb index fe193f6ccb5..8fab350e498 100644 --- a/lib/graphql/subscriptions/serialize.rb +++ b/lib/graphql/subscriptions/serialize.rb @@ -4,8 +4,7 @@ module GraphQL class Subscriptions # Serialization helpers for passing subscription data around. - # @api private - module Serialize + module Serialize # :nodoc: GLOBALID_KEY = "__gid__" SYMBOL_KEY = "__sym__" SYMBOL_KEYS_KEY = "__sym_keys__" @@ -16,23 +15,48 @@ module Serialize module_function - # @param str [String] A serialized object from {.dump} - # @return [Object] An object equivalent to the one passed to {.dump} + # **Parameters** + # + # - `str` (`String`) — A serialized object from [.dump](rdoc-ref:.dump) + # + # **Returns** + # + # - `Object` — An object equivalent to the one passed to [.dump](rdoc-ref:.dump) + # + # :call-seq: + # load(String str) -> Object 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 + # **Parameters** + # + # - `obj` (`Object`) — Some subscription-related data to dump + # + # **Returns** + # + # - `String` — The stringified object + # + # :call-seq: + # dump(Object obj) -> String 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] + # + # **Parameters** + # + # - `obj` (`Object`) + # + # **Returns** + # + # - `String` + # + # :call-seq: + # dump_recursive(Object obj) -> String def dump_recursive(obj) case when obj.is_a?(Array) @@ -53,8 +77,16 @@ def dump_recursive(obj) class << self private - # @param value [Object] A parsed JSON object - # @return [Object] An object that load Global::Identification recursive + # **Parameters** + # + # - `value` (`Object`) — A parsed JSON object + # + # **Returns** + # + # - `Object` — An object that load Global::Identification recursive + # + # :call-seq: + # load_value(Object value) -> Object def load_value(value) if value.is_a?(Array) is_gids = (v1 = value[0]).is_a?(Hash) && v1.size == 1 && v1[GLOBALID_KEY] @@ -114,8 +146,16 @@ def load_value(value) end end - # @param obj [Object] Some subscription-related data to dump - # @return [Object] The object that converted Global::Identification + # **Parameters** + # + # - `obj` (`Object`) — Some subscription-related data to dump + # + # **Returns** + # + # - `Object` — The object that converted Global::Identification + # + # :call-seq: + # dump_value(Object obj) -> Object def dump_value(obj) if obj.is_a?(Array) obj.map{|item| dump_value(item)} diff --git a/lib/graphql/testing/helpers.rb b/lib/graphql/testing/helpers.rb index be1d4c7f7cc..a352182489b 100644 --- a/lib/graphql/testing/helpers.rb +++ b/lib/graphql/testing/helpers.rb @@ -2,8 +2,16 @@ module GraphQL module Testing module Helpers - # @param schema_class [Class] - # @return [Module] A helpers module which always uses the given schema + # **Parameters** + # + # - `schema_class` (`Class`) + # + # **Returns** + # + # - `Module` — A helpers module which always uses the given schema + # + # :call-seq: + # for(Class[GraphQL::Schema] schema_class) -> Module def self.for(schema_class) SchemaHelpers.for(schema_class) end diff --git a/lib/graphql/testing/mock_action_cable.rb b/lib/graphql/testing/mock_action_cable.rb index 86fd52aa3bb..fd8a6b735a3 100644 --- a/lib/graphql/testing/mock_action_cable.rb +++ b/lib/graphql/testing/mock_action_cable.rb @@ -4,46 +4,61 @@ module Testing # A stub implementation of ActionCable. # Any methods to support the mock backend have `mock` in the name. # - # @example Configuring your schema to use MockActionCable in the test environment - # class MySchema < GraphQL::Schema - # # Use MockActionCable in test: - # use GraphQL::Subscriptions::ActionCableSubscriptions, - # action_cable: Rails.env.test? ? GraphQL::Testing::MockActionCable : ActionCable - # end + # **Examples** # - # @example Clearing old data before each test - # setup do - # GraphQL::Testing::MockActionCable.clear_mocks - # end + # **Example: Configuring your schema to use MockActionCable in the test environment** # - # @example Using MockActionCable in a test case - # # Create a channel to use in the test, pass it to GraphQL - # mock_channel = GraphQL::Testing::MockActionCable.get_mock_channel - # ActionCableTestSchema.execute("subscription { newsFlash { text } }", context: { channel: mock_channel }) + # ```ruby + # class MySchema < GraphQL::Schema + # # Use MockActionCable in test: + # use GraphQL::Subscriptions::ActionCableSubscriptions, + # action_cable: Rails.env.test? ? GraphQL::Testing::MockActionCable : ActionCable + # end + # ``` # - # # Trigger a subscription update - # ActionCableTestSchema.subscriptions.trigger(:news_flash, {}, {text: "After yesterday's rain, someone stopped on Rio Road to help a box turtle across five lanes of traffic"}) + # **Example: Clearing old data before each test** # - # # Check messages on the channel - # expected_msg = { - # result: { - # "data" => { - # "newsFlash" => { - # "text" => "After yesterday's rain, someone stopped on Rio Road to help a box turtle across five lanes of traffic" - # } - # } - # }, - # more: true, - # } - # assert_equal [expected_msg], mock_channel.mock_broadcasted_messages + # ```ruby + # setup do + # GraphQL::Testing::MockActionCable.clear_mocks + # end + # ``` + # + # **Example: Using MockActionCable in a test case** + # + # ```ruby + # # Create a channel to use in the test, pass it to GraphQL + # mock_channel = GraphQL::Testing::MockActionCable.get_mock_channel + # ActionCableTestSchema.execute("subscription { newsFlash { text } }", context: { channel: mock_channel }) # + # # Trigger a subscription update + # ActionCableTestSchema.subscriptions.trigger(:news_flash, {}, {text: "After yesterday's rain, someone stopped on Rio Road to help a box turtle across five lanes of traffic"}) + # + # # Check messages on the channel + # expected_msg = { + # result: { + # "data" => { + # "newsFlash" => { + # "text" => "After yesterday's rain, someone stopped on Rio Road to help a box turtle across five lanes of traffic" + # } + # } + # }, + # more: true, + # } + # assert_equal [expected_msg], mock_channel.mock_broadcasted_messages + # ``` class MockActionCable class MockChannel def initialize @mock_broadcasted_messages = [] end - # @return [Array] Payloads "sent" to this channel by GraphQL-Ruby + # **Returns** + # + # - `Array` — Payloads "sent" to this channel by GraphQL-Ruby + # + # :call-seq: + # mock_broadcasted_messages -> Array[Hash] attr_reader :mock_broadcasted_messages # Called by ActionCableSubscriptions. Implements a Rails API. @@ -55,8 +70,7 @@ def stream_from(stream_name, coder: nil, &block) end # Used by mock code - # @api private - class MockStream + class MockStream # :nodoc: def initialize @mock_channels = {} end @@ -96,12 +110,22 @@ def mock_stream_for(stream_name) # Use this as `context[:channel]` to simulate an ActionCable channel # - # @return [GraphQL::Testing::MockActionCable::MockChannel] + # **Returns** + # + # - `GraphQL::Testing::MockActionCable::MockChannel` + # + # :call-seq: + # get_mock_channel() -> GraphQL::Testing::MockActionCable::MockChannel def get_mock_channel MockChannel.new end - # @return [Array] Streams that currently have subscribers + # **Returns** + # + # - `Array` — Streams that currently have subscribers + # + # :call-seq: + # mock_stream_names() -> Array[String] def mock_stream_names @mock_streams.keys end diff --git a/lib/graphql/tracing.rb b/lib/graphql/tracing.rb index 56f06e9f6c8..714f10180d3 100644 --- a/lib/graphql/tracing.rb +++ b/lib/graphql/tracing.rb @@ -37,11 +37,18 @@ 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 + module Traceable # :nodoc: + # **Parameters** + # + # - `key` (`String`) — The name of the event in GraphQL internals + # - `metadata` (`Hash`) — Event-related metadata (can be anything) + # + # **Returns** + # + # - `Object` — Must return the value of the block + # + # :call-seq: + # trace(String key, Hash metadata, &block) -> Object def trace(key, metadata, &block) return yield if @tracers.empty? call_tracers(0, key, metadata, &block) @@ -52,10 +59,18 @@ def trace(key, metadata, &block) # 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 + # **Parameters** + # + # - `idx` (`Integer`) — Which tracer to call + # - `key` (`String`) — The current event name + # - `metadata` (`Object`) — The current event object + # + # **Returns** + # + # - `Object` — Whatever the block returns + # + # :call-seq: + # call_tracers(Integer idx, String key, Object metadata, &block) -> Object def call_tracers(idx, key, metadata, &block) if idx == @tracers.length yield diff --git a/lib/graphql/tracing/active_support_notifications_trace.rb b/lib/graphql/tracing/active_support_notifications_trace.rb index b5ca85ff603..5131492a20b 100644 --- a/lib/graphql/tracing/active_support_notifications_trace.rb +++ b/lib/graphql/tracing/active_support_notifications_trace.rb @@ -6,17 +6,24 @@ module GraphQL module Tracing # This implementation forwards events to ActiveSupport::Notifications with a `graphql` suffix. # - # @example Sending execution events to ActiveSupport::Notifications - # class MySchema < GraphQL::Schema - # trace_with(GraphQL::Tracing::ActiveSupportNotificationsTrace) - # end + # **Examples** # - # @example Subscribing to GraphQL events with ActiveSupport::Notifications - # ActiveSupport::Notifications.subscribe(/graphql/) do |event| - # pp event.name - # pp event.payload - # end + # **Example: Sending execution events to ActiveSupport::Notifications** # + # ```ruby + # class MySchema < GraphQL::Schema + # trace_with(GraphQL::Tracing::ActiveSupportNotificationsTrace) + # end + # ``` + # + # **Example: Subscribing to GraphQL events with ActiveSupport::Notifications** + # + # ```ruby + # ActiveSupport::Notifications.subscribe(/graphql/) do |event| + # pp event.name + # pp event.payload + # end + # ``` module ActiveSupportNotificationsTrace include NotificationsTrace def initialize(engine: ActiveSupport::Notifications, **rest) diff --git a/lib/graphql/tracing/active_support_notifications_tracing.rb b/lib/graphql/tracing/active_support_notifications_tracing.rb index 4f60c63a346..2fa4c42a6b4 100644 --- a/lib/graphql/tracing/active_support_notifications_tracing.rb +++ b/lib/graphql/tracing/active_support_notifications_tracing.rb @@ -7,7 +7,7 @@ module Tracing # This implementation forwards events to ActiveSupport::Notifications # with a `graphql` suffix. # - # @see KEYS for event names + # See [KEYS](rdoc-ref:KEYS) for event names module ActiveSupportNotificationsTracing # A cache of frequently-used keys to avoid needless string allocations KEYS = NotificationsTracing::KEYS diff --git a/lib/graphql/tracing/appsignal_trace.rb b/lib/graphql/tracing/appsignal_trace.rb index 5f60ba3c93a..97aed81daae 100644 --- a/lib/graphql/tracing/appsignal_trace.rb +++ b/lib/graphql/tracing/appsignal_trace.rb @@ -5,15 +5,23 @@ module GraphQL module Tracing # Instrumentation for reporting GraphQL-Ruby times to Appsignal. # - # @example Installing the tracer - # class MySchema < GraphQL::Schema - # trace_with GraphQL::Tracing::AppsignalTrace - # end + # **Examples** + # + # **Example: Installing the tracer** + # + # ```ruby + # class MySchema < GraphQL::Schema + # trace_with GraphQL::Tracing::AppsignalTrace + # end + # ``` AppsignalTrace = MonitorTrace.create_module("appsignal") module AppsignalTrace - # @param set_action_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_appsignal_action_name]`. + # **Parameters** + # + # - `set_action_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_appsignal_action_name]`. + # + # :call-seq: + # initialize(bool set_action_name:, **rest) def initialize(set_action_name: false, **rest) rest[:set_transaction_name] ||= set_action_name setup_appsignal_monitor(**rest) diff --git a/lib/graphql/tracing/appsignal_tracing.rb b/lib/graphql/tracing/appsignal_tracing.rb index cd552ee71a8..7ba5e5a2365 100644 --- a/lib/graphql/tracing/appsignal_tracing.rb +++ b/lib/graphql/tracing/appsignal_tracing.rb @@ -16,9 +16,12 @@ class AppsignalTracing < PlatformTracing "execute_query_lazy" => "execute.graphql", } - # @param set_action_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_appsignal_action_name]`. + # **Parameters** + # + # - `set_action_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_appsignal_action_name]`. + # + # :call-seq: + # initialize(options) def initialize(options = {}) @set_action_name = options.fetch(:set_action_name, false) super diff --git a/lib/graphql/tracing/data_dog_trace.rb b/lib/graphql/tracing/data_dog_trace.rb index 4e390f8c013..59fdcb43962 100644 --- a/lib/graphql/tracing/data_dog_trace.rb +++ b/lib/graphql/tracing/data_dog_trace.rb @@ -4,12 +4,22 @@ module GraphQL module Tracing # A tracer for reporting to DataDog - # @example Adding this tracer to your schema - # class MySchema < GraphQL::Schema - # trace_with GraphQL::Tracing::DataDogTrace - # end - # @example Skipping `resolve_type` and `authorized` events - # trace_with GraphQL::Tracing::DataDogTrace, trace_authorized: false, trace_resolve_type: false + # + # **Examples** + # + # **Example: Adding this tracer to your schema** + # + # ```ruby + # class MySchema < GraphQL::Schema + # trace_with GraphQL::Tracing::DataDogTrace + # end + # ``` + # + # **Example: Skipping `resolve_type` and `authorized` events** + # + # ```ruby + # trace_with GraphQL::Tracing::DataDogTrace, trace_authorized: false, trace_resolve_type: false + # ``` DataDogTrace = MonitorTrace.create_module("datadog") module DataDogTrace class DatadogMonitor < MonitorTrace::Monitor diff --git a/lib/graphql/tracing/data_dog_tracing.rb b/lib/graphql/tracing/data_dog_tracing.rb index b764c2ba26b..c92ab178c4c 100644 --- a/lib/graphql/tracing/data_dog_tracing.rb +++ b/lib/graphql/tracing/data_dog_tracing.rb @@ -49,9 +49,15 @@ def platform_trace(platform_key, key, data) end # Implement this method in a subclass to apply custom tags to datadog spans - # @param key [String] The event being traced - # @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event) - # @param span [Datadog::Tracing::SpanOperation] The datadog span for this event + # + # **Parameters** + # + # - `key` (`String`) — The event being traced + # - `data` (`Hash`) — The runtime data for this event (@see GraphQL::Tracing for keys for each event) + # - `span` (`Datadog::Tracing::SpanOperation`) — The datadog span for this event + # + # :call-seq: + # prepare_span(String key, Hash data, Datadog::Tracing::SpanOperation span) def prepare_span(key, data, span) end diff --git a/lib/graphql/tracing/detailed_trace.rb b/lib/graphql/tracing/detailed_trace.rb index dd3cb766869..3b4aa99c7c0 100644 --- a/lib/graphql/tracing/detailed_trace.rb +++ b/lib/graphql/tracing/detailed_trace.rb @@ -13,7 +13,7 @@ module Tracing # overriding the one in `context[:trace_mode]`. # # By default, the detailed tracer calls `.inspect` on application objects returned from fields. You can customize - # this behavior by extending {DetailedTrace} and overriding {#inspect_object}. You can opt out of debug annotations + # this behavior by extending [DetailedTrace](rdoc-ref:DetailedTrace) and overriding [inspect object](rdoc-ref:#inspect_object). You can opt out of debug annotations # entirely with `use ..., debug: false` or for a single query with `context: { detailed_trace_debug: false }`. # # You can store saved traces in two ways: @@ -26,46 +26,67 @@ module Tracing # # If you need to save traces indefinitely, you can download them from Perfetto after opening them there. # - # @example Installing with Rails - # rails generate graphql:detailed_trace # optional: --redis + # See [Graphql::Dashboard](rdoc-ref:Graphql::Dashboard) GraphQL::Dashboard for viewing stored results # - # @example Adding the sampler to your schema - # class MySchema < GraphQL::Schema - # # Add the sampler: - # use GraphQL::Tracing::DetailedTrace, redis: Redis.new(...), limit: 100 + # **Examples** # - # # And implement this hook to tell it when to take a sample: - # def self.detailed_trace?(query) - # # Could use `query.context`, `query.selected_operation_name`, `query.query_string` here - # # Could call out to Flipper, etc - # rand <= 0.000_1 # one in ten thousand - # end + # **Example: Installing with Rails** + # + # ```ruby + # rails generate graphql:detailed_trace # optional: --redis + # ``` + # + # **Example: Adding the sampler to your schema** + # + # ```ruby + # class MySchema < GraphQL::Schema + # # Add the sampler: + # use GraphQL::Tracing::DetailedTrace, redis: Redis.new(...), limit: 100 + # + # # And implement this hook to tell it when to take a sample: + # def self.detailed_trace?(query) + # # Could use `query.context`, `query.selected_operation_name`, `query.query_string` here + # # Could call out to Flipper, etc + # rand <= 0.000_1 # one in ten thousand # end + # end + # ``` + # + # **Example: Customizing debug output in traces** # - # @see Graphql::Dashboard GraphQL::Dashboard for viewing stored results - # - # @example Customizing debug output in traces - # class CustomDetailedTrace < GraphQL::Tracing::DetailedTrace - # def inspect_object(object) - # if object.is_a?(SomeThing) - # # handle it specially ... - # else - # super - # end - # end + # ```ruby + # class CustomDetailedTrace < GraphQL::Tracing::DetailedTrace + # def inspect_object(object) + # if object.is_a?(SomeThing) + # # handle it specially ... + # else + # super + # end # end + # end + # ``` + # + # **Example: disabling debug annotations completely** # - # @example disabling debug annotations completely - # use DetailedTrace, debug: false, ... + # ```ruby + # use DetailedTrace, debug: false, ... + # ``` # - # @example disabling debug annotations for one query - # MySchema.execute(query_str, context: { detailed_trace_debug: false }) + # **Example: disabling debug annotations for one query** # + # ```ruby + # MySchema.execute(query_str, context: { detailed_trace_debug: false }) + # ``` class DetailedTrace - # @param redis [Redis] If provided, profiles will be stored in Redis for later review - # @param limit [Integer] A maximum number of profiles to store - # @param debug [Boolean] if `false`, it won't create `debug` annotations in Perfetto traces (reduces overhead) - # @param model_class [Class] Overrides {ActiveRecordBackend::GraphqlDetailedTrace} if present + # **Parameters** + # + # - `redis` (`Redis`) — If provided, profiles will be stored in Redis for later review + # - `limit` (`Integer`) — A maximum number of profiles to store + # - `debug` (`Boolean`) — if `false`, it won't create `debug` annotations in Perfetto traces (reduces overhead) + # - `model_class` (`Class`) — Overrides [ActiveRecordBackend::GraphqlDetailedTrace](rdoc-ref:ActiveRecordBackend::GraphqlDetailedTrace) if present + # + # :call-seq: + # use(schema, trace_mode:, memory:, bool debug:, Redis redis:, Integer limit:, Class[ActiveRecord::Base] model_class:) def self.use(schema, trace_mode: :profile_sample, memory: false, debug: debug?, redis: nil, limit: nil, model_class: nil) storage = if redis RedisBackend.new(redis: redis, limit: limit) @@ -87,37 +108,75 @@ def initialize(storage:, trace_mode:, debug:) @debug = debug end - # @return [Symbol] The trace mode to use when {Schema.detailed_trace?} returns `true` + # **Returns** + # + # - `Symbol` — The trace mode to use when [Schema.detailed_trace?](rdoc-ref:Schema.detailed_trace?) returns `true` + # + # :call-seq: + # trace_mode -> Symbol attr_reader :trace_mode - # @return [String] ID of saved trace + # **Returns** + # + # - `String` — ID of saved trace + # + # :call-seq: + # save_trace(operation_name, duration_ms, begin_ms, trace_data) -> String def save_trace(operation_name, duration_ms, begin_ms, trace_data) @storage.save_trace(operation_name, duration_ms, begin_ms, trace_data) end - # @return [Boolean] + # **Returns** + # + # - `Boolean` + # + # :call-seq: + # debug?() -> bool def debug? @debug end - # @param last [Integer] - # @param before [Integer] Timestamp in milliseconds since epoch - # @return [Enumerable] + # **Parameters** + # + # - `last` (`Integer`) + # - `before` (`Integer`) — Timestamp in milliseconds since epoch + # + # **Returns** + # + # - `Enumerable` + # + # :call-seq: + # traces(Integer last:, Integer before:) -> Enumerable[StoredTrace] def traces(last: nil, before: nil) @storage.traces(last: last, before: before) end - # @return [StoredTrace, nil] + # **Returns** + # + # - `StoredTrace, nil` + # + # :call-seq: + # find_trace(id) -> StoredTrace | nil def find_trace(id) @storage.find_trace(id) end - # @return [void] + # **Returns** + # + # - `void` + # + # :call-seq: + # delete_trace(id) -> void def delete_trace(id) @storage.delete_trace(id) end - # @return [void] + # **Returns** + # + # - `void` + # + # :call-seq: + # delete_all_traces() -> void def delete_all_traces @storage.delete_all_traces end @@ -135,7 +194,13 @@ def self.inspect_object(object) end # Default debug setting - # @return [true] + # + # **Returns** + # + # - `true` + # + # :call-seq: + # debug?() -> true def self.debug? true end diff --git a/lib/graphql/tracing/monitor_trace.rb b/lib/graphql/tracing/monitor_trace.rb index 2d374583f2b..79732a9bee1 100644 --- a/lib/graphql/tracing/monitor_trace.rb +++ b/lib/graphql/tracing/monitor_trace.rb @@ -5,7 +5,7 @@ module Tracing # This module is the basis for Ruby-level integration with third-party monitoring platforms. # Platform-specific traces include this module and implement an adapter. # - # @see ActiveSupportNotificationsTrace Integration via ActiveSupport::Notifications, an alternative approach. + # See [ActiveSupportNotificationsTrace](rdoc-ref:ActiveSupportNotificationsTrace) Integration via ActiveSupport::Notifications, an alternative approach. module MonitorTrace class Monitor def initialize(trace:, set_transaction_name:, **_rest) @@ -147,10 +147,15 @@ def self.create_module(monitor_name) end MODULE_TEMPLATE = <<~RUBY - # @param set_transaction_name [Boolean] If `true`, use the GraphQL operation name as the request name on the monitoring platform - # @param trace_scalars [Boolean] If `true`, leaf fields will be traced too (Scalars _and_ Enums) - # @param trace_authorized [Boolean] If `false`, skip tracing `authorized?` calls - # @param trace_resolve_type [Boolean] If `false`, skip tracing `resolve_type?` calls + # **Parameters** + # + # - `set_transaction_name` (`Boolean`) — If `true`, use the GraphQL operation name as the request name on the monitoring platform + # - `trace_scalars` (`Boolean`) — If `true`, leaf fields will be traced too (Scalars _and_ Enums) + # - `trace_authorized` (`Boolean`) — If `false`, skip tracing `authorized?` calls + # - `trace_resolve_type` (`Boolean`) — If `false`, skip tracing `resolve_type?` calls + # + # :call-seq: + # initialize(...) def initialize(...) setup_%{monitor}_monitor(...) super diff --git a/lib/graphql/tracing/new_relic_trace.rb b/lib/graphql/tracing/new_relic_trace.rb index 745f5469d8f..9d5667f71b8 100644 --- a/lib/graphql/tracing/new_relic_trace.rb +++ b/lib/graphql/tracing/new_relic_trace.rb @@ -6,16 +6,24 @@ module GraphQL module Tracing # A tracer for reporting GraphQL-Ruby time to New Relic # - # @example Installing the tracer - # class MySchema < GraphQL::Schema - # trace_with GraphQL::Tracing::NewRelicTrace + # **Examples** # - # # Optional, use the operation name to set the new relic transaction name: - # # trace_with GraphQL::Tracing::NewRelicTrace, set_transaction_name: true - # end + # **Example: Installing the tracer** # - # @example Installing without trace events for `authorized?` or `resolve_type` calls - # trace_with GraphQL::Tracing::NewRelicTrace, trace_authorized: false, trace_resolve_type: false + # ```ruby + # class MySchema < GraphQL::Schema + # trace_with GraphQL::Tracing::NewRelicTrace + # + # # Optional, use the operation name to set the new relic transaction name: + # # trace_with GraphQL::Tracing::NewRelicTrace, set_transaction_name: true + # end + # ``` + # + # **Example: Installing without trace events for `authorized?` or `resolve_type` calls** + # + # ```ruby + # trace_with GraphQL::Tracing::NewRelicTrace, trace_authorized: false, trace_resolve_type: false + # ``` NewRelicTrace = MonitorTrace.create_module("newrelic") module NewRelicTrace class NewrelicMonitor < MonitorTrace::Monitor diff --git a/lib/graphql/tracing/new_relic_tracing.rb b/lib/graphql/tracing/new_relic_tracing.rb index a2d05b5203a..b71c89b09d3 100644 --- a/lib/graphql/tracing/new_relic_tracing.rb +++ b/lib/graphql/tracing/new_relic_tracing.rb @@ -16,9 +16,12 @@ class NewRelicTracing < PlatformTracing "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]`. + # **Parameters** + # + # - `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]`. + # + # :call-seq: + # initialize(options) def initialize(options = {}) @set_transaction_name = options.fetch(:set_transaction_name, false) super diff --git a/lib/graphql/tracing/notifications_trace.rb b/lib/graphql/tracing/notifications_trace.rb index 6c592ba9c8e..1c035fabedf 100644 --- a/lib/graphql/tracing/notifications_trace.rb +++ b/lib/graphql/tracing/notifications_trace.rb @@ -5,10 +5,9 @@ module Tracing # This implementation forwards events to a notification handler # (i.e. ActiveSupport::Notifications or Dry::Monitor::Notifications) with a `graphql` suffix. # - # @see ActiveSupportNotificationsTrace ActiveSupport::Notifications integration + # See [ActiveSupportNotificationsTrace](rdoc-ref:ActiveSupportNotificationsTrace) ActiveSupport::Notifications integration module NotificationsTrace - # @api private - class Adapter + class Adapter # :nodoc: def instrument(keyword, payload, &block) raise "Implement #{self.class}#instrument to measure the block" end @@ -37,8 +36,7 @@ def finish end end - # @api private - class DryMonitorAdapter < Adapter + class DryMonitorAdapter < Adapter # :nodoc: def instrument(...) Dry::Monitor.instrument(...) end @@ -54,8 +52,7 @@ def finish end end - # @api private - class ActiveSupportNotificationsAdapter < Adapter + class ActiveSupportNotificationsAdapter < Adapter # :nodoc: def instrument(...) ActiveSupport::Notifications.instrument(...) end @@ -73,7 +70,12 @@ def finish end end - # @param engine [Class] The notifications engine to use, eg `Dry::Monitor` or `ActiveSupport::Notifications` + # **Parameters** + # + # - `engine` (`Class`) — The notifications engine to use, eg `Dry::Monitor` or `ActiveSupport::Notifications` + # + # :call-seq: + # initialize(Class engine:, **rest) def initialize(engine:, **rest) adapter = if defined?(Dry::Monitor) && engine == Dry::Monitor DryMonitoringAdapter diff --git a/lib/graphql/tracing/notifications_tracing.rb b/lib/graphql/tracing/notifications_tracing.rb index c8ad4f88612..f6b43a64fc1 100644 --- a/lib/graphql/tracing/notifications_tracing.rb +++ b/lib/graphql/tracing/notifications_tracing.rb @@ -8,7 +8,7 @@ module Tracing # ActiveSupport::Notifications or Dry::Monitor::Notifications) # with a `graphql` suffix. # - # @see KEYS for event names + # See [KEYS](rdoc-ref:KEYS) for event names class NotificationsTracing # A cache of frequently-used keys to avoid needless string allocations KEYS = { @@ -31,21 +31,36 @@ class NotificationsTracing # Initialize a new NotificationsTracing instance # - # @param [Object] notifications_engine The notifications engine to use + # **Parameters** + # + # - `notifications_engine` (`Object`) — The notifications engine to use + # + # :call-seq: + # initialize(Object notifications_engine) def initialize(notifications_engine) @notifications_engine = notifications_engine end # Sends a GraphQL tracing event to the notification handler # - # @example - # . notifications_engine = Dry::Monitor::Notifications.new(:graphql) + # **Yields:** The block to execute for the event + # + # **Examples** + # + # **Example: . notifications_engine = Dry::Monitor::Notifications.new(:graphql)** + # + # ```ruby # . tracer = GraphQL::Tracing::NotificationsTracing.new(notifications_engine) # . tracer.trace("lex") { ... } + # ``` + # + # **Parameters** + # + # - `key` (`string`) — The key for the event + # - `metadata` (`Hash`) — The metadata for the event # - # @param [string] key The key for the event - # @param [Hash] metadata The metadata for the event - # @yield The block to execute for the event + # :call-seq: + # trace(string key, Hash metadata, &blk) def trace(key, metadata, &blk) prefixed_key = KEYS[key] || "#{key}.graphql" diff --git a/lib/graphql/tracing/perfetto_trace.rb b/lib/graphql/tracing/perfetto_trace.rb index 29fc3f44457..84ab68894c3 100644 --- a/lib/graphql/tracing/perfetto_trace.rb +++ b/lib/graphql/tracing/perfetto_trace.rb @@ -3,30 +3,35 @@ module GraphQL module Tracing # This produces a trace file for inspecting in the [Perfetto Trace Viewer](https://ui.perfetto.dev). # - # To get the file, call {#write} on the trace. + # To get the file, call [write](rdoc-ref:#write) on the trace. # # Use "trace modes" to configure this to run on command or on a sample of traffic. # - # @example Writing trace output + # **Examples** # - # result = MySchema.execute(...) - # result.query.trace.write(file: "tmp/trace.dump") + # **Example: Writing trace output** # - # @example Running this instrumenter when `trace: true` is present in the request + # ```ruby + # result = MySchema.execute(...) + # result.query.trace.write(file: "tmp/trace.dump") + # ``` # - # class MySchema < GraphQL::Schema - # # Only run this tracer when `context[:trace_mode]` is `:trace` - # trace_with GraphQL::Tracing::Perfetto, mode: :trace - # end + # **Example: Running this instrumenter when `trace: true` is present in the request** # - # # In graphql_controller.rb: + # ```ruby + # class MySchema < GraphQL::Schema + # # Only run this tracer when `context[:trace_mode]` is `:trace` + # trace_with GraphQL::Tracing::Perfetto, mode: :trace + # end # - # context[:trace_mode] = params[:trace] ? :trace : nil - # result = MySchema.execute(query_str, context: context, variables: variables, ...) - # if context[:trace_mode] == :trace - # result.trace.write(file: ...) - # end + # # In graphql_controller.rb: # + # context[:trace_mode] = params[:trace] ? :trace : nil + # result = MySchema.execute(query_str, context: context, variables: variables, ...) + # if context[:trace_mode] == :trace + # result.trace.write(file: ...) + # end + # ``` module PerfettoTrace # TODOs: # - Make debug annotations visible on both parts when dataloader is involved @@ -74,7 +79,12 @@ def self.included(_trace_class) DEBUG_INSPECT_EVENT_NAME_IID = 17 DA_DEBUG_INSPECT_FOR_IID = 18 - # @param active_support_notifications_pattern [String, RegExp, false] A filter for `ActiveSupport::Notifications`, if it's present. Or `false` to skip subscribing. + # **Parameters** + # + # - `active_support_notifications_pattern` (`String, RegExp, false`) — A filter for `ActiveSupport::Notifications`, if it's present. Or `false` to skip subscribing. + # + # :call-seq: + # initialize(String | RegExp | false active_support_notifications_pattern:, save_profile:, **_rest) def initialize(active_support_notifications_pattern: nil, save_profile: false, **_rest) super @active_support_notifications_pattern = active_support_notifications_pattern @@ -571,9 +581,18 @@ def end_resolve_type(type, value, context, resolved_type) end # Dump protobuf output in the specified file. - # @param file [String] path to a file in a directory that already exists - # @param debug_json [Boolean] True to print JSON instead of binary - # @return [nil, String, Hash] If `file` was given, `nil`. If `file` was `nil`, a Hash if `debug_json: true`, else binary data. + # + # **Parameters** + # + # - `file` (`String`) — path to a file in a directory that already exists + # - `debug_json` (`Boolean`) — True to print JSON instead of binary + # + # **Returns** + # + # - `nil, String, Hash` — If `file` was given, `nil`. If `file` was `nil`, a Hash if `debug_json: true`, else binary data. + # + # :call-seq: + # write(String file:, bool debug_json:) -> nil | String | Hash def write(file:, debug_json: false) trace = Trace.new( packet: @packets, diff --git a/lib/graphql/tracing/platform_tracing.rb b/lib/graphql/tracing/platform_tracing.rb index 6f0984d92e2..95a9732ef9f 100644 --- a/lib/graphql/tracing/platform_tracing.rb +++ b/lib/graphql/tracing/platform_tracing.rb @@ -6,8 +6,7 @@ module Tracing # - `.platform_keys` # - `#platform_trace` # - `#platform_field_key(type, field)` - # @api private - class PlatformTracing + class PlatformTracing # :nodoc: class << self attr_accessor :platform_keys @@ -123,10 +122,18 @@ def fallback_transaction_name(context) # # If the key isn't present, the given block is called and the result is cached for `key`. # - # @param ctx [GraphQL::Query::Context] - # @param key [Class, GraphQL::Field] A part of the schema - # @param trace_phase [Symbol] The stage of execution being traced (used by OpenTelementry tracing) - # @return [String] + # **Parameters** + # + # - `ctx` (`GraphQL::Query::Context`) + # - `key` (`Class, GraphQL::Field`) — A part of the schema + # - `trace_phase` (`Symbol`) — The stage of execution being traced (used by OpenTelementry tracing) + # + # **Returns** + # + # - `String` + # + # :call-seq: + # cached_platform_key(GraphQL::Query::Context ctx, Class | GraphQL::Field key, Symbol trace_phase) -> String def cached_platform_key(ctx, key, trace_phase) cache = ctx.namespace(self.class)[:platform_key_cache] ||= {} cache.fetch(key) { cache[key] = yield } diff --git a/lib/graphql/tracing/prometheus_trace.rb b/lib/graphql/tracing/prometheus_trace.rb index 57cd342327e..db4e5f7750b 100644 --- a/lib/graphql/tracing/prometheus_trace.rb +++ b/lib/graphql/tracing/prometheus_trace.rb @@ -8,24 +8,32 @@ module Tracing # # The PrometheusExporter server must be run with a custom type collector that extends `GraphQL::Tracing::PrometheusTracing::GraphQLCollector`. # - # @example Adding this trace to your schema - # require 'prometheus_exporter/client' + # **Examples** # - # class MySchema < GraphQL::Schema - # trace_with GraphQL::Tracing::PrometheusTrace - # end + # **Example: Adding this trace to your schema** + # + # ```ruby + # require 'prometheus_exporter/client' + # + # class MySchema < GraphQL::Schema + # trace_with GraphQL::Tracing::PrometheusTrace + # end + # ``` + # + # **Example: Running a custom type collector** # - # @example Running a custom type collector - # # lib/graphql_collector.rb - # if defined?(PrometheusExporter::Server) - # require 'graphql/tracing' + # ```ruby + # # lib/graphql_collector.rb + # if defined?(PrometheusExporter::Server) + # require 'graphql/tracing' # - # class GraphQLCollector < GraphQL::Tracing::PrometheusTrace::GraphQLCollector - # end + # class GraphQLCollector < GraphQL::Tracing::PrometheusTrace::GraphQLCollector # end + # end # - # # Then run: - # # bundle exec prometheus_exporter -a lib/graphql_collector.rb + # # Then run: + # # bundle exec prometheus_exporter -a lib/graphql_collector.rb + # ``` PrometheusTrace = MonitorTrace.create_module("prometheus") module PrometheusTrace if defined?(PrometheusExporter::Server) diff --git a/lib/graphql/tracing/scout_trace.rb b/lib/graphql/tracing/scout_trace.rb index e3af68477ec..2433c043687 100644 --- a/lib/graphql/tracing/scout_trace.rb +++ b/lib/graphql/tracing/scout_trace.rb @@ -6,10 +6,15 @@ module GraphQL module Tracing # A tracer for sending GraphQL-Ruby times to Scout # - # @example Adding this tracer to your schema - # class MySchema < GraphQL::Schema - # trace_with GraphQL::Tracing::ScoutTrace - # end + # **Examples** + # + # **Example: Adding this tracer to your schema** + # + # ```ruby + # class MySchema < GraphQL::Schema + # trace_with GraphQL::Tracing::ScoutTrace + # end + # ``` ScoutTrace = MonitorTrace.create_module("scout") module ScoutTrace class ScoutMonitor < MonitorTrace::Monitor diff --git a/lib/graphql/tracing/scout_tracing.rb b/lib/graphql/tracing/scout_tracing.rb index c3b20b7ee98..393a1203801 100644 --- a/lib/graphql/tracing/scout_tracing.rb +++ b/lib/graphql/tracing/scout_tracing.rb @@ -18,9 +18,12 @@ class ScoutTracing < PlatformTracing "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]`. + # **Parameters** + # + # - `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]`. + # + # :call-seq: + # initialize(options) def initialize(options = {}) self.class.include ScoutApm::Tracer @set_transaction_name = options.fetch(:set_transaction_name, false) diff --git a/lib/graphql/tracing/sentry_trace.rb b/lib/graphql/tracing/sentry_trace.rb index 68fb0d734ed..fbb80801bb7 100644 --- a/lib/graphql/tracing/sentry_trace.rb +++ b/lib/graphql/tracing/sentry_trace.rb @@ -6,11 +6,17 @@ module GraphQL module Tracing # A tracer for reporting GraphQL-Ruby times to Sentry. # - # @example Installing the tracer - # class MySchema < GraphQL::Schema - # trace_with GraphQL::Tracing::SentryTrace - # end - # @see MonitorTrace Configuration Options in the parent module + # See [MonitorTrace](rdoc-ref:MonitorTrace) Configuration Options in the parent module + # + # **Examples** + # + # **Example: Installing the tracer** + # + # ```ruby + # class MySchema < GraphQL::Schema + # trace_with GraphQL::Tracing::SentryTrace + # end + # ``` SentryTrace = MonitorTrace.create_module("sentry") module SentryTrace class SentryMonitor < MonitorTrace::Monitor diff --git a/lib/graphql/tracing/statsd_trace.rb b/lib/graphql/tracing/statsd_trace.rb index f1148b80ad8..3d1e98b0d1d 100644 --- a/lib/graphql/tracing/statsd_trace.rb +++ b/lib/graphql/tracing/statsd_trace.rb @@ -8,12 +8,17 @@ module Tracing # Passing any Statsd client that implements `.time(name) { ... }` # and `.timing(name, ms)` will work. # - # @example Installing this tracer - # # eg: - # # $statsd = Statsd.new 'localhost', 9125 - # class MySchema < GraphQL::Schema - # use GraphQL::Tracing::StatsdTrace, statsd: $statsd - # end + # **Examples** + # + # **Example: Installing this tracer** + # + # ```ruby + # # eg: + # # $statsd = Statsd.new 'localhost', 9125 + # class MySchema < GraphQL::Schema + # use GraphQL::Tracing::StatsdTrace, statsd: $statsd + # end + # ``` StatsdTrace = MonitorTrace.create_module("statsd") module StatsdTrace class StatsdMonitor < MonitorTrace::Monitor diff --git a/lib/graphql/tracing/statsd_tracing.rb b/lib/graphql/tracing/statsd_tracing.rb index 2b1ff10c8d0..9fc054444e2 100644 --- a/lib/graphql/tracing/statsd_tracing.rb +++ b/lib/graphql/tracing/statsd_tracing.rb @@ -16,7 +16,12 @@ class StatsdTracing < PlatformTracing 'execute_query_lazy' => "graphql.execute_query_lazy", } - # @param statsd [Object] A statsd client + # **Parameters** + # + # - `statsd` (`Object`) — A statsd client + # + # :call-seq: + # initialize(Object statsd:, **rest) def initialize(statsd:, **rest) @statsd = statsd super(**rest) diff --git a/lib/graphql/tracing/trace.rb b/lib/graphql/tracing/trace.rb index 54490fcddf9..9d9c999b045 100644 --- a/lib/graphql/tracing/trace.rb +++ b/lib/graphql/tracing/trace.rb @@ -11,8 +11,13 @@ module Tracing # to continue any tracing hooks and call the actual runtime behavior. # class Trace - # @param multiplex [GraphQL::Execution::Multiplex, nil] - # @param query [GraphQL::Query, nil] + # **Parameters** + # + # - `multiplex` (`GraphQL::Execution::Multiplex, nil`) + # - `query` (`GraphQL::Query, nil`) + # + # :call-seq: + # initialize(GraphQL::Execution::Multiplex | nil multiplex:, GraphQL::Query | nil query:, **_options) def initialize(multiplex: nil, query: nil, **_options) @multiplex = multiplex @query = query @@ -23,8 +28,16 @@ def lex(query_string:) yield end - # @param query_string [String] - # @return [void] + # **Parameters** + # + # - `query_string` (`String`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # parse(String query_string:) -> void def parse(query_string:) yield end @@ -39,16 +52,40 @@ def begin_validate(query, validate) def end_validate(query, validate, errors) end - # @param multiplex [GraphQL::Execution::Multiplex] - # @param analyzers [Array] - # @return [void] + # **Parameters** + # + # - `multiplex` (`GraphQL::Execution::Multiplex`) + # - `analyzers` (`Array`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # begin_analyze_multiplex(GraphQL::Execution::Multiplex multiplex, Array[Class] analyzers) -> void def begin_analyze_multiplex(multiplex, analyzers); end - # @param multiplex [GraphQL::Execution::Multiplex] - # @param analyzers [Array] - # @return [void] + # **Parameters** + # + # - `multiplex` (`GraphQL::Execution::Multiplex`) + # - `analyzers` (`Array`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # end_analyze_multiplex(GraphQL::Execution::Multiplex multiplex, Array[Class] analyzers) -> void def end_analyze_multiplex(multiplex, analyzers); end - # @param multiplex [GraphQL::Execution::Multiplex] - # @return [void] + # **Parameters** + # + # - `multiplex` (`GraphQL::Execution::Multiplex`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # analyze_multiplex(GraphQL::Execution::Multiplex multiplex:) -> void def analyze_multiplex(multiplex:) yield end @@ -58,8 +95,17 @@ def analyze_query(query:) end # This wraps an entire `.execute` call. - # @param multiplex [GraphQL::Execution::Multiplex] - # @return [void] + # + # **Parameters** + # + # - `multiplex` (`GraphQL::Execution::Multiplex`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # execute_multiplex(GraphQL::Execution::Multiplex multiplex:) -> void def execute_multiplex(multiplex:) yield end @@ -73,17 +119,29 @@ def execute_query_lazy(query:, multiplex:) end # GraphQL is about to resolve this field - # @param field [GraphQL::Schema::Field] - # @param object [GraphQL::Schema::Object] - # @param arguments [Hash] - # @param query [GraphQL::Query] + # + # **Parameters** + # + # - `field` (`GraphQL::Schema::Field`) + # - `object` (`GraphQL::Schema::Object`) + # - `arguments` (`Hash`) + # - `query` (`GraphQL::Query`) + # + # :call-seq: + # begin_execute_field(GraphQL::Schema::Field field, GraphQL::Schema::Object object, Hash arguments, GraphQL::Query query) def begin_execute_field(field, object, arguments, query); end # GraphQL just finished resolving this field - # @param field [GraphQL::Schema::Field] - # @param object [GraphQL::Schema::Object] - # @param arguments [Hash] - # @param query [GraphQL::Query] - # @param result [Object] + # + # **Parameters** + # + # - `field` (`GraphQL::Schema::Field`) + # - `object` (`GraphQL::Schema::Object`) + # - `arguments` (`Hash`) + # - `query` (`GraphQL::Query`) + # - `result` (`Object`) + # + # :call-seq: + # end_execute_field(GraphQL::Schema::Field field, GraphQL::Schema::Object object, Hash arguments, GraphQL::Query query, Object result) def end_execute_field(field, object, arguments, query, result); end def execute_field(field:, query:, ast_node:, arguments:, object:) @@ -105,18 +163,36 @@ def object_loaded(argument_definition, object, context) end # A call to `.authorized?` is starting - # @param type [Class] - # @param object [Object] - # @param context [GraphQL::Query::Context] - # @return [void] + # + # **Parameters** + # + # - `type` (`Class`) + # - `object` (`Object`) + # - `context` (`GraphQL::Query::Context`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # begin_authorized(Class[GraphQL::Schema::Object] type, Object object, GraphQL::Query::Context context) -> void def begin_authorized(type, object, context) end # A call to `.authorized?` just finished - # @param type [Class] - # @param object [Object] - # @param context [GraphQL::Query::Context] - # @param authorized_result [Boolean] - # @return [void] + # + # **Parameters** + # + # - `type` (`Class`) + # - `object` (`Object`) + # - `context` (`GraphQL::Query::Context`) + # - `authorized_result` (`Boolean`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # end_authorized(Class[GraphQL::Schema::Object] type, Object object, GraphQL::Query::Context context, bool authorized_result) -> void def end_authorized(type, object, context, authorized_result) end @@ -133,59 +209,155 @@ def resolve_type_lazy(query:, type:, object:) end # A call to `.resolve_type` is starting - # @param type [Class, Module] - # @param value [Object] - # @param context [GraphQL::Query::Context] - # @return [void] + # + # **Parameters** + # + # - `type` (`Class, Module`) + # - `value` (`Object`) + # - `context` (`GraphQL::Query::Context`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # begin_resolve_type(Class[GraphQL::Schema::Union] | Module[GraphQL::Schema::Interface] type, Object value, GraphQL::Query::Context context) -> void def begin_resolve_type(type, value, context) end # A call to `.resolve_type` just ended - # @param type [Class, Module] - # @param value [Object] - # @param context [GraphQL::Query::Context] - # @param resolved_type [Class] - # @return [void] + # + # **Parameters** + # + # - `type` (`Class, Module`) + # - `value` (`Object`) + # - `context` (`GraphQL::Query::Context`) + # - `resolved_type` (`Class`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # end_resolve_type(Class[GraphQL::Schema::Union] | Module[GraphQL::Schema::Interface] type, Object value, GraphQL::Query::Context context, Class[GraphQL::Schema::Object] resolved_type) -> void def end_resolve_type(type, value, context, resolved_type) end # A dataloader run is starting - # @param dataloader [GraphQL::Dataloader] - # @return [void] + # + # **Parameters** + # + # - `dataloader` (`GraphQL::Dataloader`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # begin_dataloader(GraphQL::Dataloader dataloader) -> void def begin_dataloader(dataloader); end # A dataloader run has ended - # @param dataloder [GraphQL::Dataloader] - # @return [void] + # + # **Parameters** + # + # - `dataloder` (`GraphQL::Dataloader`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # end_dataloader(dataloader) -> void def end_dataloader(dataloader); end # A source with pending keys is about to fetch - # @param source [GraphQL::Dataloader::Source] - # @return [void] + # + # **Parameters** + # + # - `source` (`GraphQL::Dataloader::Source`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # begin_dataloader_source(GraphQL::Dataloader::Source source) -> void def begin_dataloader_source(source); end # A fetch call has just ended - # @param source [GraphQL::Dataloader::Source] - # @return [void] + # + # **Parameters** + # + # - `source` (`GraphQL::Dataloader::Source`) + # + # **Returns** + # + # - `void` + # + # :call-seq: + # end_dataloader_source(GraphQL::Dataloader::Source source) -> void def end_dataloader_source(source); end # Called when Dataloader spins up a new fiber for GraphQL execution - # @param jobs [Array<#call>] Execution steps to run - # @return [void] + # + # **Parameters** + # + # - `jobs` (`Array<#call>`) — Execution steps to run + # + # **Returns** + # + # - `void` + # + # :call-seq: + # dataloader_spawn_execution_fiber(Array[#call] jobs) -> void def dataloader_spawn_execution_fiber(jobs); end # Called when Dataloader spins up a new fiber for fetching data - # @param pending_sources [GraphQL::Dataloader::Source] Instances with pending keys - # @return [void] + # + # **Parameters** + # + # - `pending_sources` (`GraphQL::Dataloader::Source`) — Instances with pending keys + # + # **Returns** + # + # - `void` + # + # :call-seq: + # dataloader_spawn_source_fiber(GraphQL::Dataloader::Source pending_sources) -> void def dataloader_spawn_source_fiber(pending_sources); end # Called when an execution or source fiber terminates - # @return [void] + # + # **Returns** + # + # - `void` + # + # :call-seq: + # dataloader_fiber_exit;() -> void def dataloader_fiber_exit; end # Called when a Dataloader fiber is paused to wait for data - # @param source [GraphQL::Dataloader::Source] The Source whose `load` call initiated this `yield` - # @return [void] + # + # **Parameters** + # + # - `source` (`GraphQL::Dataloader::Source`) — The Source whose `load` call initiated this `yield` + # + # **Returns** + # + # - `void` + # + # :call-seq: + # dataloader_fiber_yield(GraphQL::Dataloader::Source source) -> void def dataloader_fiber_yield(source); end # Called when a Dataloader fiber is resumed because data has been loaded - # @param source [GraphQL::Dataloader::Source] The Source whose `load` call previously caused this Fiber to wait - # @return [void] + # + # **Parameters** + # + # - `source` (`GraphQL::Dataloader::Source`) — The Source whose `load` call previously caused this Fiber to wait + # + # **Returns** + # + # - `void` + # + # :call-seq: + # dataloader_fiber_resume(GraphQL::Dataloader::Source source) -> void def dataloader_fiber_resume(source); end end end diff --git a/lib/graphql/type_kinds.rb b/lib/graphql/type_kinds.rb index 3c052c4d8b2..79da6075cd6 100644 --- a/lib/graphql/type_kinds.rb +++ b/lib/graphql/type_kinds.rb @@ -18,7 +18,7 @@ def initialize(name, abstract: false, leaf: false, fields: false, wraps: false, end # Does this TypeKind have multiple possible implementers? - # @deprecated Use `abstract?` instead of `resolves?`. + # **Deprecated:** Use `abstract?` instead of `resolves?`. def resolves?; @abstract; end # Is this TypeKind abstract? def abstract?; @abstract; end diff --git a/lib/graphql/types/int.rb b/lib/graphql/types/int.rb index e9ec3d55311..5d69df011d1 100644 --- a/lib/graphql/types/int.rb +++ b/lib/graphql/types/int.rb @@ -2,7 +2,7 @@ module GraphQL module Types - # @see {Types::BigInt} for handling integers outside 32-bit range. + # See [Types::BigInt](rdoc-ref: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." diff --git a/lib/graphql/types/iso_8601_date.rb b/lib/graphql/types/iso_8601_date.rb index f2b45071b35..a7581af9817 100644 --- a/lib/graphql/types/iso_8601_date.rb +++ b/lib/graphql/types/iso_8601_date.rb @@ -16,14 +16,30 @@ class ISO8601Date < GraphQL::Schema::Scalar description "An ISO 8601-encoded date" specified_by_url "https://tools.ietf.org/html/rfc3339" - # @param value [Date,Time,DateTime,String] - # @return [String] + # **Parameters** + # + # - `value` (`Date, Time, DateTime, String`) + # + # **Returns** + # + # - `String` + # + # :call-seq: + # coerce_result(Date | Time | DateTime | String value, _ctx) -> String def self.coerce_result(value, _ctx) Date.parse(value.to_s).iso8601 end - # @param str_value [String, Date, DateTime, Time] - # @return [Date, nil] + # **Parameters** + # + # - `str_value` (`String, Date, DateTime, Time`) + # + # **Returns** + # + # - `Date, nil` + # + # :call-seq: + # coerce_input(value, ctx) -> Date | nil def self.coerce_input(value, ctx) if value.is_a?(::Date) value diff --git a/lib/graphql/types/iso_8601_date_time.rb b/lib/graphql/types/iso_8601_date_time.rb index 73421734e49..28c3e023381 100644 --- a/lib/graphql/types/iso_8601_date_time.rb +++ b/lib/graphql/types/iso_8601_date_time.rb @@ -23,18 +23,36 @@ class ISO8601DateTime < GraphQL::Schema::Scalar # i.e. ActiveSupport::JSON::Encoder.time_precision (3 by default) DEFAULT_TIME_PRECISION = 0 - # @return [Integer] + # **Returns** + # + # - `Integer` + # + # :call-seq: + # time_precision() -> Integer def self.time_precision @time_precision || DEFAULT_TIME_PRECISION end - # @param [Integer] value + # **Parameters** + # + # - `value` (`Integer`) + # + # :call-seq: + # time_precision=(Integer value) def self.time_precision=(value) @time_precision = value end - # @param value [Time,Date,DateTime,String] - # @return [String] + # **Parameters** + # + # - `value` (`Time, Date, DateTime, String`) + # + # **Returns** + # + # - `String` + # + # :call-seq: + # coerce_result(Time | Date | DateTime | String value, _ctx) -> String def self.coerce_result(value, _ctx) case value when Date @@ -49,8 +67,16 @@ def self.coerce_result(value, _ctx) 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] + # **Parameters** + # + # - `str_value` (`String`) + # + # **Returns** + # + # - `Time` + # + # :call-seq: + # coerce_input(String str_value, _ctx) -> Time def self.coerce_input(str_value, _ctx) Time.iso8601(str_value) rescue ArgumentError, TypeError diff --git a/lib/graphql/types/iso_8601_duration.rb b/lib/graphql/types/iso_8601_duration.rb index 9c128900d07..af6086bde42 100644 --- a/lib/graphql/types/iso_8601_duration.rb +++ b/lib/graphql/types/iso_8601_duration.rb @@ -16,20 +16,41 @@ module Types class ISO8601Duration < GraphQL::Schema::Scalar description "An ISO 8601-encoded duration" - # @return [Integer, nil] + # **Returns** + # + # - `Integer, nil` + # + # :call-seq: + # seconds_precision() -> Integer | nil def self.seconds_precision # ActiveSupport::Duration precision defaults to whatever input was given @seconds_precision end - # @param [Integer, nil] value + # **Parameters** + # + # - `value` (`Integer, nil`) + # + # :call-seq: + # seconds_precision=(Integer | nil value) def self.seconds_precision=(value) @seconds_precision = value end - # @param value [ActiveSupport::Duration, String] - # @return [String] - # @raise [GraphQL::Error] if ActiveSupport::Duration is not defined or if an incompatible object is passed + # **Parameters** + # + # - `value` (`ActiveSupport::Duration, String`) + # + # **Returns** + # + # - `String` + # + # **Raises** + # + # - `GraphQL::Error` — if ActiveSupport::Duration is not defined or if an incompatible object is passed + # + # :call-seq: + # coerce_result(ActiveSupport::Duration | String value, _ctx) -> String | GraphQL::Error def self.coerce_result(value, _ctx) unless defined?(ActiveSupport::Duration) raise GraphQL::Error, "ActiveSupport >= 5.0 must be loaded to use the built-in ISO8601Duration type." @@ -50,10 +71,21 @@ def self.coerce_result(value, _ctx) end end - # @param value [String, ActiveSupport::Duration] - # @return [ActiveSupport::Duration, nil] - # @raise [GraphQL::Error] if ActiveSupport::Duration is not defined - # @raise [GraphQL::DurationEncodingError] if duration cannot be parsed + # **Parameters** + # + # - `value` (`String, ActiveSupport::Duration`) + # + # **Returns** + # + # - `ActiveSupport::Duration, nil` + # + # **Raises** + # + # - `GraphQL::Error` — if ActiveSupport::Duration is not defined + # - `GraphQL::DurationEncodingError` — if duration cannot be parsed + # + # :call-seq: + # coerce_input(String | ActiveSupport::Duration value, ctx) -> ActiveSupport::Duration | nil | GraphQL::Error | GraphQL::DurationEncodingError def self.coerce_input(value, ctx) unless defined?(ActiveSupport::Duration) raise GraphQL::Error, "ActiveSupport >= 5.0 must be loaded to use the built-in ISO8601Duration type." diff --git a/lib/graphql/types/relay/base_connection.rb b/lib/graphql/types/relay/base_connection.rb index 46d60e88e2e..0226e78cc80 100644 --- a/lib/graphql/types/relay/base_connection.rb +++ b/lib/graphql/types/relay/base_connection.rb @@ -9,38 +9,43 @@ module Relay # 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 - # class BaseObject < GraphQL::Schema::Object; end - # - # # 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, - # edges_nullable: true, - # edge_nullable: true, - # node_nullable: true, - # nodes_field: true - # - # # Alternatively, you can call the class methods followed by your edge type - # # edges_nullable true - # # edge_nullable true - # # node_nullable true - # # has_nodes_field true - # # edge_type Types::PostEdge - # end - # - # @see Relay::BaseEdge for edge types + # See [Relay::BaseEdge](rdoc-ref:Relay::BaseEdge) for edge types + # + # **Examples** + # + # **Example: Implementation a connection and edge** + # + # ```ruby + # class BaseObject < GraphQL::Schema::Object; end + # + # # 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, + # edges_nullable: true, + # edge_nullable: true, + # node_nullable: true, + # nodes_field: true + # + # # Alternatively, you can call the class methods followed by your edge type + # # edges_nullable true + # # edge_nullable true + # # node_nullable true + # # has_nodes_field true + # # edge_type Types::PostEdge + # end + # ``` class BaseConnection < Schema::Object include ConnectionBehaviors end diff --git a/lib/graphql/types/relay/base_edge.rb b/lib/graphql/types/relay/base_edge.rb index 046c733a0a3..a3dd7bfa65b 100644 --- a/lib/graphql/types/relay/base_edge.rb +++ b/lib/graphql/types/relay/base_edge.rb @@ -10,17 +10,22 @@ module Relay # 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 + # See [GraphQL::Types::Relay::BaseConnection](rdoc-ref:GraphQL::Types::Relay::BaseConnection) for connection types # - # # Then extend your own base class - # class Types::UserEdge < Types::BaseEdge - # node_type(Types::User) - # end + # **Examples** # - # @see {Relay::BaseConnection} for connection types + # **Example: Making a UserEdge type** + # + # ```ruby + # # 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 + # ``` class BaseEdge < GraphQL::Schema::Object include Types::Relay::EdgeBehaviors end diff --git a/lib/graphql/types/relay/connection_behaviors.rb b/lib/graphql/types/relay/connection_behaviors.rb index a855282cfe8..0cbde813099 100644 --- a/lib/graphql/types/relay/connection_behaviors.rb +++ b/lib/graphql/types/relay/connection_behaviors.rb @@ -47,10 +47,20 @@ def default_broadcastable(new_value) @default_broadcastable = new_value end - # @return [Class] + # **Returns** + # + # - `Class` + # + # :call-seq: + # node_type -> Class attr_reader :node_type - # @return [Class] + # **Returns** + # + # - `Class` + # + # :call-seq: + # edge_class -> Class attr_reader :edge_class # Configure this connection to return `edges` and `nodes` based on `edge_type_class`. @@ -63,7 +73,13 @@ def default_broadcastable(new_value) # 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). - # @param field_options [Hash] Any extra keyword arguments to pass to the `field :edges, ...` and `field :nodes, ...` configurations + # + # **Parameters** + # + # - `field_options` (`Hash`) — Any extra keyword arguments to pass to the `field :edges, ...` and `field :nodes, ...` configurations + # + # :call-seq: + # edge_type(edge_type_class, edge_class:, node_type:, nodes_field:, node_nullable:, edges_nullable:, edge_nullable:, Hash field_options:) def edge_type(edge_type_class, edge_class: GraphQL::Pagination::Connection::Edge, node_type: edge_type_class.node_type, nodes_field: self.has_nodes_field, node_nullable: self.node_nullable, edges_nullable: self.edges_nullable, edge_nullable: self.edge_nullable, field_options: nil) # Set this connection's graphql name node_type_name = node_type.graphql_name diff --git a/lib/graphql/types/relay/edge_behaviors.rb b/lib/graphql/types/relay/edge_behaviors.rb index 0734e145664..46d47ac3820 100644 --- a/lib/graphql/types/relay/edge_behaviors.rb +++ b/lib/graphql/types/relay/edge_behaviors.rb @@ -43,9 +43,14 @@ def default_broadcastable(new_value) # Get or set the Object type that this edge wraps. # - # @param node_type [Class] A `Schema::Object` subclass - # @param null [Boolean] - # @param field_options [Hash] Any extra arguments to pass to the `field :node` configuration + # **Parameters** + # + # - `node_type` (`Class`) — A `Schema::Object` subclass + # - `null` (`Boolean`) + # - `field_options` (`Hash`) — Any extra arguments to pass to the `field :node` configuration + # + # :call-seq: + # node_type(Class node_type, bool null:, Hash field_options:) def node_type(node_type = nil, null: self.node_nullable, field_options: nil) if node_type @node_type = node_type diff --git a/lib/graphql/unauthorized_enum_value_error.rb b/lib/graphql/unauthorized_enum_value_error.rb index f3bfc2acbf1..d65c1dba5d3 100644 --- a/lib/graphql/unauthorized_enum_value_error.rb +++ b/lib/graphql/unauthorized_enum_value_error.rb @@ -1,7 +1,12 @@ # frozen_string_literal: true module GraphQL class UnauthorizedEnumValueError < GraphQL::UnauthorizedError - # @return [GraphQL::Schema::EnumValue] The value whose `#authorized?` check returned false + # **Returns** + # + # - `GraphQL::Schema::EnumValue` — The value whose `#authorized?` check returned false + # + # :call-seq: + # enum_value -> GraphQL::Schema::EnumValue attr_accessor :enum_value def initialize(type:, context:, enum_value:) diff --git a/lib/graphql/unauthorized_error.rb b/lib/graphql/unauthorized_error.rb index 56cf79b2b43..0d9fc6d0270 100644 --- a/lib/graphql/unauthorized_error.rb +++ b/lib/graphql/unauthorized_error.rb @@ -5,13 +5,28 @@ module GraphQL # # Alternatively, custom code in `authorized?` may raise this error. It will be routed the same way. class UnauthorizedError < GraphQL::RuntimeError - # @return [Object] the application object that failed the authorization check + # **Returns** + # + # - `Object` — the application object that failed the authorization check + # + # :call-seq: + # object -> Object attr_reader :object - # @return [Class] the GraphQL object type whose `.authorized?` method was called (and returned false) + # **Returns** + # + # - `Class` — the GraphQL object type whose `.authorized?` method was called (and returned false) + # + # :call-seq: + # type -> Class attr_reader :type - # @return [GraphQL::Query::Context] the context for the current query + # **Returns** + # + # - `GraphQL::Query::Context` — the context for the current query + # + # :call-seq: + # context -> GraphQL::Query::Context attr_accessor :context def initialize(message = nil, object: nil, type: nil, context: nil) diff --git a/lib/graphql/unauthorized_field_error.rb b/lib/graphql/unauthorized_field_error.rb index 5d5c8bdb071..98a63065611 100644 --- a/lib/graphql/unauthorized_field_error.rb +++ b/lib/graphql/unauthorized_field_error.rb @@ -1,7 +1,12 @@ # frozen_string_literal: true module GraphQL class UnauthorizedFieldError < GraphQL::UnauthorizedError - # @return [Field] the field that failed the authorization check + # **Returns** + # + # - `Field` — the field that failed the authorization check + # + # :call-seq: + # field -> Field attr_accessor :field def initialize(message = nil, object: nil, type: nil, context: nil, field: nil) diff --git a/lib/graphql/unresolved_type_error.rb b/lib/graphql/unresolved_type_error.rb index 3b8e95025b5..a7f37400d84 100644 --- a/lib/graphql/unresolved_type_error.rb +++ b/lib/graphql/unresolved_type_error.rb @@ -3,19 +3,44 @@ 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` + # **Returns** + # + # - `Object` — The runtime value which couldn't be successfully resolved with `resolve_type` + # + # :call-seq: + # value -> Object attr_reader :value - # @return [GraphQL::Field] The field whose value couldn't be resolved (`field.type` is type which couldn't be resolved) + # **Returns** + # + # - `GraphQL::Field` — The field whose value couldn't be resolved (`field.type` is type which couldn't be resolved) + # + # :call-seq: + # field -> GraphQL::Field attr_reader :field - # @return [GraphQL::BaseType] The owner of `field` + # **Returns** + # + # - `GraphQL::BaseType` — The owner of `field` + # + # :call-seq: + # parent_type -> GraphQL::BaseType attr_reader :parent_type - # @return [Object] The return of {Schema#resolve_type} for `value` + # **Returns** + # + # - `Object` — The return of [Schema.resolve_type](rdoc-ref:GraphQL::Schema::resolve_type) for `value` + # + # :call-seq: + # resolved_type -> Object attr_reader :resolved_type - # @return [Array] The allowed options for resolving `value` to `field.type` + # **Returns** + # + # - `Array` — The allowed options for resolving `value` to `field.type` + # + # :call-seq: + # possible_types -> Array[GraphQL::BaseType] attr_reader :possible_types def initialize(value, field, parent_type, resolved_type, possible_types) diff --git a/spec/docs/compatibility_spec.rb b/spec/docs/compatibility_spec.rb new file mode 100644 index 00000000000..fb2ffc93586 --- /dev/null +++ b/spec/docs/compatibility_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "json" +require "tmpdir" +require_relative "../spec_helper" +require_relative "../../tool/docs/compatibility" + +describe GraphQLDocs::Compatibility do + it "compares the RDoc index with the committed YARD API baseline" do + Dir.mktmpdir("graphql-docs") do |directory| + File.write(File.join(directory, "GraphQL.html"), '

execute

') + index_path = File.join(directory, "search_data.js") + File.write(index_path, <<~JS) + var search_data = {"index":[ + {"full_name":"GraphQL::Schema","type":"class","path":"GraphQL.html","snippet":"Schema"}, + {"full_name":"GraphQL::Schema::execute","type":"class_method","path":"GraphQL.html#method-c-execute","snippet":"execute"} + ]}; + JS + baseline_path = File.join(directory, "baseline.yml") + File.write(baseline_path, <<~YAML) + version: 1 + source: test baseline + allowlist_review: + reviewed_at: "2026-08-09" + missing_reason: test + extra_reason: test + yard_api: + - [class, GraphQL::Schema] + - [class_method, GraphQL::Schema.execute] + allowed_missing: [] + allowed_extra: [] + YAML + + result = GraphQLDocs::Compatibility.new(root: directory).check(rdoc_index: index_path, baseline: baseline_path) + + _(result.fetch("unexpected_baseline_missing")).must_equal [] + _(result.fetch("unexpected_baseline_extra")).must_equal [] + end + end +end diff --git a/spec/docs/generator_spec.rb b/spec/docs/generator_spec.rb new file mode 100644 index 00000000000..e82e3ae92ab --- /dev/null +++ b/spec/docs/generator_spec.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +require "tmpdir" +require_relative "../spec_helper" +require_relative "../../tool/docs/generator" + +describe RDoc::Generator::GraphQLRuby do + it "deduplicates API entries while retaining guide entries" do + generator = RDoc::Generator::GraphQLRuby.allocate + entries = generator.send(:deduplicate_search_entries, [ + { type: "class", full_name: "GraphQL::Schema" }, + { type: "class", full_name: "GraphQL::Schema" }, + { type: "guide", full_name: "Guide: Getting Started" }, + ]) + + _(entries.map { |entry| entry[:full_name] }).must_equal ["GraphQL::Schema", "Guide: Getting Started"] + end + + it "preserves API visibility text and escapes search snippets" do + generator = RDoc::Generator::GraphQLRuby.allocate + + private_entry = generator.send(:sanitize_search_entry, { + type: "class", + full_name: "GraphQL::Internal", + snippet: "

API: private

", + }) + _(private_entry[:snippet]).must_equal "API: private" + + entry = generator.send(:sanitize_search_entry, { + type: "class", + full_name: "GraphQL::Schema", + "snippet" => "

See [Schema](rdoc-ref:GraphQL::Schema)

", + }) + _([entry[:snippet], entry["snippet"]].compact.first).must_equal "See Schema alert(1)" + end + + it "removes unresolved rdoc-ref pseudo-URLs while preserving labels" do + Dir.mktmpdir("graphql-docs") do |directory| + path = File.join(directory, "index.html") + File.write(path, '

[Schema](rdoc-ref:GraphQL::Schema)

rdoc-ref:Query#initialize') + + generator = RDoc::Generator::GraphQLRuby.allocate + generator.send(:remove_unresolved_rdoc_refs, Pathname.new(path)) + + html = File.read(path) + _(html).must_include("Schema") + _(html).must_include("Query#initialize") + _(html).wont_include("rdoc-ref:") + end + end + + it "restores attributes for inline HTML images in generated headings" do + Dir.mktmpdir("graphql-docs") do |directory| + path = File.join(directory, "readme_md.html") + File.write(path, '

graphql <img src=“” height=“40” alt=“graphql-ruby”/>

') + + generator = RDoc::Generator::GraphQLRuby.allocate + generator.send(:normalize_malformed_html_images, Pathname.new(path)) + + html = File.read(path) + _(html).must_include('graphql-ruby') + _(html).wont_include('<img src=') + end + end + + it "makes root-relative image paths local-file friendly" do + Dir.mktmpdir("graphql-docs") do |directory| + output = Pathname.new(directory) + path = output.join("guides/object_cache/overview_md.html") + path.dirname.mkpath + File.write(path, '

') + + generator = RDoc::Generator::GraphQLRuby.allocate + generator.send(:normalize_root_relative_image_paths, path, output) + + _(File.read(path)).must_include('') + end + end + + it "nests guide directories in the sidebar" do + Dir.mktmpdir("graphql-docs") do |directory| + output = Pathname.new(directory) + authorization = output.join("guides/authorization/overview_md.html") + dataloader = output.join("guides/dataloader/overview_md.html") + authorization.dirname.mkpath + dataloader.dirname.mkpath + sidebar = <<~HTML + + HTML + File.write(authorization, sidebar) + + generator = RDoc::Generator::GraphQLRuby.allocate + page_titles = { + "guides/authorization/overview_md.html" => "Authorization Overview", + "guides/dataloader/overview_md.html" => "Dataloader Overview", + "docs/maintenance_md.html" => "Documentation maintenance", + "readme_md.html" => "README", + } + generator.send(:normalize_page_titles_in_sidebar, authorization, output, page_titles) + generator.send(:nest_guide_pages_in_sidebar, authorization, output, page_titles) + + html = File.read(authorization) + _(html).must_include('Authorization') + _(html).must_include('Dataloader') + _(html).must_include('Guides') + _(html).must_match(/
\s*Authorization<\/summary>/) + _(html).must_include("Authorization Overview") + _(html).must_include("Dataloader Overview") + _(html).must_include("Documentation maintenance") + _(html).must_match(/>\s*README\s*Guide') + + _(GraphQLDocs::LinkChecker.new(directory).check.size).must_equal 1 + _(GraphQLDocs::LinkChecker.new(directory, allow_root_links: true).check).must_equal [] + end + end + + it "validates root links against a canonical published site when provided" do + Dir.mktmpdir("graphql-docs") do |directory| + canonical = Dir.mktmpdir("graphql-docs-canonical") + File.write(File.join(directory, "index.html"), 'Guide') + FileUtils.mkdir_p(File.join(canonical, "schema")) + File.write(File.join(canonical, "schema", "definition.html"), "

Guide

") + + checker = GraphQLDocs::LinkChecker.new(directory, allow_root_links: true, root_links: canonical) + _(checker.check).must_equal [] + ensure + FileUtils.remove_entry(canonical) if canonical && File.directory?(canonical) + end + end +end diff --git a/spec/docs/migrate_guides_spec.rb b/spec/docs/migrate_guides_spec.rb new file mode 100644 index 00000000000..2600c58f376 --- /dev/null +++ b/spec/docs/migrate_guides_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require_relative "../spec_helper" +require_relative "../../tool/docs/migrate_guides" + +describe GraphQLDocs::GuideMigrator do + it "converts front matter and documentation liquid tags" do + source = <<~MARKDOWN + --- + title: Executing Queries + section: Queries + --- + + Use {{ "GraphQL::Schema" | api_doc }} and {% internal_link "the guide", "/queries/executing_queries" %}. + + {{ "/images/query.png" | link_to_img:"Query" }} + + {% callout warning %} + Be careful. + {% endcallout %} + MARKDOWN + result = GraphQLDocs::GuideMigrator.new(paths: []).migrate(source) + _(result).must_include("# Executing Queries") + _(result).must_include("[GraphQL::Schema](rdoc-ref:GraphQL::Schema)") + _(result).must_include("[the guide](/queries/executing_queries)") + _(result).must_include("![Query](/images/query.png)") + _(result).must_include("> **Warning:**") + _(result).wont_include("{{") + _(result).wont_include("{%") + end +end diff --git a/spec/docs/publish_check_spec.rb b/spec/docs/publish_check_spec.rb new file mode 100644 index 00000000000..a6c3a1ac9a6 --- /dev/null +++ b/spec/docs/publish_check_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "tmpdir" +require_relative "../spec_helper" +require_relative "../../tool/docs/publish_check" + +describe GraphQLDocs::PublishCheck do + it "allows a new version while preserving existing API documentation" do + Dir.mktmpdir("graphql-docs") do |directory| + pages = File.join(directory, "pages") + old_file = File.join(pages, "api-doc", "1.0.0", "index.html") + new_file = File.join(pages, "api-doc", "2.0.0", "index.html") + FileUtils.mkdir_p(File.dirname(old_file)) + File.write(old_file, "old API") + checker = GraphQLDocs::PublishCheck.new(pages: pages) + snapshot = checker.snapshot + FileUtils.mkdir_p(File.dirname(new_file)) + File.write(new_file, "new API") + + errors, changed = checker.verify(snapshot: snapshot, allowed_version: "2.0.0", expected_version: "2.0.0") + _(errors).must_equal [] + _(changed).must_equal ["api-doc/2.0.0/index.html"] + end + end + + it "rejects changes to an existing version" do + Dir.mktmpdir("graphql-docs") do |directory| + pages = File.join(directory, "pages") + file = File.join(pages, "api-doc", "1.0.0", "index.html") + FileUtils.mkdir_p(File.dirname(file)) + File.write(file, "old API") + checker = GraphQLDocs::PublishCheck.new(pages: pages) + snapshot = checker.snapshot + File.write(file, "modified API") + + errors, = checker.verify(snapshot: snapshot) + _(errors).wont_be_empty + end + end +end diff --git a/spec/docs/rdoc_ref_checker_spec.rb b/spec/docs/rdoc_ref_checker_spec.rb new file mode 100644 index 00000000000..39cf96d5b91 --- /dev/null +++ b/spec/docs/rdoc_ref_checker_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "tmpdir" +require_relative "../spec_helper" +require_relative "../../tool/docs/rdoc_ref_checker" + +describe GraphQLDocs::RDocReferenceChecker do + it "finds references that leaked into generated HTML" do + Dir.mktmpdir("graphql-docs") do |directory| + File.write(File.join(directory, "index.html"), "

rdoc-ref:GraphQL::Schema

") + result = GraphQLDocs::RDocReferenceChecker.new(directory).check + _(result).must_equal [{ "file" => "index.html", "reference" => "rdoc-ref:GraphQL::Schema" }] + end + end + + it "also checks the Aliki search index" do + Dir.mktmpdir("graphql-docs") do |directory| + FileUtils.mkdir_p(File.join(directory, "js")) + File.write(File.join(directory, "js", "search_data.js"), "var search_data = 'rdoc-ref:GraphQL::Schema';") + result = GraphQLDocs::RDocReferenceChecker.new(directory).check + _([result.first["file"], result.first["reference"]]).must_equal ["js/search_data.js", "rdoc-ref:GraphQL::Schema"] + end + end + + it "accepts HTML without unresolved references" do + Dir.mktmpdir("graphql-docs") do |directory| + File.write(File.join(directory, "index.html"), "

GraphQL::Schema

") + _(GraphQLDocs::RDocReferenceChecker.new(directory).check).must_equal [] + end + end +end diff --git a/spec/docs/redirect_spec.rb b/spec/docs/redirect_spec.rb new file mode 100644 index 00000000000..912870a4ab6 --- /dev/null +++ b/spec/docs/redirect_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require "json" +require "pathname" +require "tmpdir" +require_relative "../spec_helper" +require_relative "../../tool/docs/legacy" +require_relative "../../tool/docs/redirects" + +describe GraphQLDocs::LegacyAnchors do + it "adds legacy method anchors from the RDoc index" do + Dir.mktmpdir("graphql-docs") do |directory| + root = Pathname.new(directory) + FileUtils.mkdir_p(root.join("js")) + File.write(root.join("js/search_data.js"), <<~JS) + var search_data = {"index":[{"full_name":"GraphQL::Schema#execute","type":"instance_method","path":"GraphQL/Schema.html#method-i-execute"}]}; + JS + FileUtils.mkdir_p(root.join("GraphQL")) + File.write(root.join("GraphQL/Schema.html"), '') + GraphQLDocs::LegacyAnchors.install(root: root) + _(File.read(root.join("GraphQL/Schema.html"))).must_include('id="execute-instance_method"') + end + end +end + +describe GraphQLDocs::Redirects do + it "writes clean and html redirect entry points" do + Dir.mktmpdir("graphql-docs") do |directory| + root = Pathname.new(directory) + output = root.join("site") + FileUtils.mkdir_p(root.join("docs")) + File.write(root.join("docs/redirects.yml"), <<~YAML) + redirects: + - old_path: /queries/executing_queries + destination: + kind: page + value: guides/queries/executing_queries.md + YAML + GraphQLDocs::Redirects.generate(root: root, output: output) + index = output.join("queries/executing_queries/index.html") + html = output.join("queries/executing_queries.html") + _(index).must_be :file? + _(html).must_be :file? + _(File.read(index)).must_include("guides/queries/executing_queries_md.html") + _(File.read(index)).must_include("window.location.replace") + end + end +end diff --git a/spec/docs/type_signatures_spec.rb b/spec/docs/type_signatures_spec.rb new file mode 100644 index 00000000000..63b6eb7e280 --- /dev/null +++ b/spec/docs/type_signatures_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require_relative "../spec_helper" +require_relative "../../tool/docs/type_signatures" + +describe GraphQLDocs::TypeSignatureMigrator do + it "converts migrated YARD type sections into RDoc call sequences" do + source = <<~RUBY + class Example + # Does something. + # + # **Parameters** + # + # - `value` (`String`) + # + # **Returns** + # + # - `Array` + def call(value) + [value] + end + end + RUBY + + result = GraphQLDocs::TypeSignatureMigrator.new(paths: []).migrate(source) + + _(result).must_include("# :call-seq:") + _(result).must_include("# call(String value) -> Array[String]") + _(result).must_include("def call(value)") + end + + it "keeps nested generic types and Ruby parameter prefixes intact" do + source = <<~RUBY + class Example + # **Parameters** + # + # - `options` (`Hash Array>`) + # - `block` (`Proc`) + # + # **Returns** + # + # - `Boolean` + def call(options = {}, &block) + block.call(options) + end + end + RUBY + result = GraphQLDocs::TypeSignatureMigrator.new(paths: []).migrate(source) + _(result).must_include("# call(Hash[String, Array[Integer]] options, Proc &block) -> bool") + end + + it "does not add signatures to nodoc objects" do + source = <<~RUBY + class Example + # :nodoc: + # **Returns** + # + # - `String` + def call + "hidden" + end + end + RUBY + + result = GraphQLDocs::TypeSignatureMigrator.new(paths: []).migrate(source) + + _(result).wont_include(":call-seq:") + end +end diff --git a/spec/docs/version_check_spec.rb b/spec/docs/version_check_spec.rb new file mode 100644 index 00000000000..c14076a33cc --- /dev/null +++ b/spec/docs/version_check_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "tmpdir" +require_relative "../spec_helper" +require_relative "../../tool/docs/version_check" + +describe GraphQLDocs::VersionCheck do + it "validates a generated versioned API index" do + Dir.mktmpdir("graphql-docs") do |directory| + FileUtils.mkdir_p(File.join(directory, "js")) + File.write(File.join(directory, "index.html"), "") + File.write(File.join(directory, "js", "search_data.js"), 'var search_data = {"index":[{"full_name":"GraphQL::Schema"}]};') + errors = GraphQLDocs::VersionCheck.new(root: directory, version: "1.2.3").check + _(errors).must_equal [] + end + end + + it "rejects unresolved references in versioned output" do + Dir.mktmpdir("graphql-docs") do |directory| + FileUtils.mkdir_p(File.join(directory, "js")) + File.write(File.join(directory, "index.html"), "

rdoc-ref:GraphQL::Schema

") + File.write(File.join(directory, "js", "search_data.js"), 'var search_data = {"index":[{"full_name":"GraphQL::Schema"}]};') + errors = GraphQLDocs::VersionCheck.new(root: directory, version: "1.2.3").check + _(errors).must_include("versioned output contains unresolved RDoc references") + end + end +end diff --git a/spec/graphql/schema/argument_spec.rb b/spec/graphql/schema/argument_spec.rb index 080c8565f82..3319d884072 100644 --- a/spec/graphql/schema/argument_spec.rb +++ b/spec/graphql/schema/argument_spec.rb @@ -857,7 +857,7 @@ class Query < GraphQL::Schema::Object describe "argument definitions" do it "HasArguments::argument documents each argument" do has_arguments_argument_comment = File.read("./lib/graphql/schema/member/has_arguments.rb")[/(\s+#[^\n]*\n)+\s+def argument\(/m] - has_arguments_argument_doc_param_names = has_arguments_argument_comment.split("\n").map { |line| (line[/@param (\S+)/] || line[/@option kwargs \[.*\] :(\S+)/]); $1 }.compact + has_arguments_argument_doc_param_names = rdoc_parameter_names(has_arguments_argument_comment, include_options: true) argument_initialize_argument_names = GraphQL::Schema::Argument.instance_method(:initialize).parameters.map { |param| param[1].to_s } assert_equal ["kwargs"], has_arguments_argument_doc_param_names - argument_initialize_argument_names assert_equal ["owner"], argument_initialize_argument_names - has_arguments_argument_doc_param_names @@ -865,7 +865,7 @@ class Query < GraphQL::Schema::Object it "Argument::initialize documents each argument" do argument_initialize_comment = File.read("./lib/graphql/schema/argument.rb")[/(\s+#[^\n]*\n)+ {6}def initialize\(/m] - argument_initialize_doc_param_names = argument_initialize_comment.split("\n").map { |line| line[/@param (\S+)/]; $1 }.compact + argument_initialize_doc_param_names = rdoc_parameter_names(argument_initialize_comment) argument_initialize_argument_names = GraphQL::Schema::Argument.instance_method(:initialize).parameters.map { |param| param[1].to_s } assert_equal argument_initialize_doc_param_names.sort, argument_initialize_argument_names.sort end diff --git a/spec/graphql/schema/field_spec.rb b/spec/graphql/schema/field_spec.rb index 2a425213bf4..961872fc24f 100644 --- a/spec/graphql/schema/field_spec.rb +++ b/spec/graphql/schema/field_spec.rb @@ -951,10 +951,7 @@ class Connection < GraphQL::Schema::Object; end describe "argument documentation" do it "HasFields::field documents each argument" do has_fields_field_comment = File.read("./lib/graphql/schema/member/has_fields.rb")[/(\s+#[^\n]*\n)+\s+def field\(/m] - has_field_field_doc_param_names = has_fields_field_comment.split("\n").map do |line| - line[/@param (\S+)/] || line[/@option kwargs \[.*\] :(\S+)/] - $1 - end.compact + has_field_field_doc_param_names = rdoc_parameter_names(has_fields_field_comment, include_options: true) field_initialize_argument_names = GraphQL::Schema::Field.instance_method(:initialize).parameters.map { |param| param[1].to_s } @@ -967,13 +964,13 @@ class Connection < GraphQL::Schema::Object; end "subscription", "kwargs", ] - assert_equal expected_differences, has_field_field_doc_param_names - field_initialize_argument_names + assert_equal expected_differences.sort, (has_field_field_doc_param_names - field_initialize_argument_names).sort assert_equal ["owner", "resolver_class"], field_initialize_argument_names - has_field_field_doc_param_names end it "Field::initialize documents each argument" do field_initialize_comment = File.read("./lib/graphql/schema/field.rb")[/(\s+#[^\n]*\n)+ {6}def initialize\(/m] - field_initialize_doc_param_names = field_initialize_comment.split("\n").map { |line| line[/@param (\S+)/]; $1 }.compact + field_initialize_doc_param_names = rdoc_parameter_names(field_initialize_comment) field_initialize_argument_names = GraphQL::Schema::Field.instance_method(:initialize).parameters.map { |param| param[1].to_s } assert_equal field_initialize_doc_param_names.sort, field_initialize_argument_names.sort end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 840aaa60b76..00632e526f1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -61,6 +61,24 @@ def if_exec_next(exec_next_value, legacy_value) TESTING_EXEC_NEXT ? exec_next_value : legacy_value end +def rdoc_parameter_names(comment, include_options: false) + section = nil + comment.lines.filter_map do |line| + if line.include?("**Parameters**") + section = :parameters + nil + elsif line.include?("**Options**") + section = :options + nil + elsif line.match?(/\*\*(?:Yields|Returns)\*\*/) + section = nil + nil + elsif section == :parameters || (include_options && section == :options) + line[/^\s*# - `([^`]+)`/, 1]&.sub(/\Akwargs\.:/, "") + end + end +end + module Minitest class Test # These tests are skipped but should be fixed at some point diff --git a/tool/docs/assets/graphql_highlighter.css b/tool/docs/assets/graphql_highlighter.css new file mode 100644 index 00000000000..3ff74223b2a --- /dev/null +++ b/tool/docs/assets/graphql_highlighter.css @@ -0,0 +1,18 @@ +/* graphql-ruby: graphql highlighter */ +pre.graphql .graphql-keyword { color: #8250df; font-weight: 600; } +pre.graphql .graphql-literal { color: #cf222e; } +pre.graphql .graphql-variable { color: #0550ae; } +pre.graphql .graphql-directive { color: #953800; } +pre.graphql .graphql-number { color: #0550ae; } +pre.graphql .graphql-string { color: #0a3069; } +pre.graphql .graphql-comment { color: #6e7781; font-style: italic; } +pre.graphql .graphql-punctuation { color: #57606a; } +@media (prefers-color-scheme: dark) { + pre.graphql .graphql-keyword { color: #d2a8ff; } + pre.graphql .graphql-literal { color: #ff7b72; } + pre.graphql .graphql-variable, pre.graphql .graphql-number { color: #79c0ff; } + pre.graphql .graphql-directive { color: #ffa657; } + pre.graphql .graphql-string { color: #a5d6ff; } + pre.graphql .graphql-comment { color: #8b949e; } + pre.graphql .graphql-punctuation { color: #c9d1d9; } +} diff --git a/tool/docs/assets/graphql_highlighter.js b/tool/docs/assets/graphql_highlighter.js new file mode 100644 index 00000000000..677f1250119 --- /dev/null +++ b/tool/docs/assets/graphql_highlighter.js @@ -0,0 +1,75 @@ +'use strict'; + +/* graphql-ruby: graphql highlighter */ +(function(root) { + const KEYWORDS = new Set(['query', 'mutation', 'subscription', 'fragment', 'on', 'schema', 'type', 'interface', 'union', 'enum', 'input', 'scalar', 'directive', 'extend', 'implements']); + const LITERALS = new Set(['true', 'false', 'null']); + + function escapeHTML(value) { + return value.replace(/[&<>"']/g, (character) => ({ + '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' + })[character]); + } + + function span(className, value) { + return `${escapeHTML(value)}`; + } + + function highlightGraphQL(source) { + let html = ''; + let index = 0; + while (index < source.length) { + const rest = source.slice(index); + let match; + if (rest.startsWith('"""')) { + const end = source.indexOf('"""', index + 3); + const finish = end < 0 ? source.length : end + 3; + html += span('string', source.slice(index, finish)); + index = finish; + } else if (source[index] === '"') { + match = source.slice(index).match(/^"(?:\\.|[^"\\])*"/s); + const value = match ? match[0] : source.slice(index); + html += span('string', value); + index += value.length; + } else if (source[index] === '#') { + const end = source.indexOf('\n', index); + const finish = end < 0 ? source.length : end; + html += span('comment', source.slice(index, finish)); + index = finish; + } else if ((match = rest.match(/^\$[_A-Za-z][_0-9A-Za-z]*/))) { + html += span('variable', match[0]); + index += match[0].length; + } else if ((match = rest.match(/^@[_A-Za-z][_0-9A-Za-z]*/))) { + html += span('directive', match[0]); + index += match[0].length; + } else if ((match = rest.match(/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/))) { + html += span('number', match[0]); + index += match[0].length; + } else if ((match = rest.match(/^[_A-Za-z][_0-9A-Za-z]*/))) { + const value = match[0]; + html += span(KEYWORDS.has(value) ? 'keyword' : (LITERALS.has(value) ? 'literal' : 'name'), value); + index += value.length; + } else if ((match = rest.match(/^[!$():=@\[\]{|}&]/))) { + html += span('punctuation', match[0]); + index += 1; + } else { + html += escapeHTML(source[index]); + index += 1; + } + } + return html; + } + + function highlightGraphQLDocument(document) { + document.querySelectorAll('pre.graphql').forEach((element) => { + if (element.dataset.graphqlHighlighted === 'true') return; + const source = element.textContent; + element.innerHTML = highlightGraphQL(source); + element.dataset.graphqlHighlighted = 'true'; + }); + } + + root.GraphQLRubyHighlighter = { escapeHTML, highlightGraphQL, highlightGraphQLDocument }; + if (root.document) root.document.addEventListener('DOMContentLoaded', () => highlightGraphQLDocument(root.document)); + if (typeof module !== 'undefined') module.exports = root.GraphQLRubyHighlighter; +}(typeof globalThis === 'undefined' ? this : globalThis)); diff --git a/tool/docs/assets/graphql_highlighter_test.js b/tool/docs/assets/graphql_highlighter_test.js new file mode 100644 index 00000000000..e9866202c50 --- /dev/null +++ b/tool/docs/assets/graphql_highlighter_test.js @@ -0,0 +1,24 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { highlightGraphQL } = require('./graphql_highlighter.js'); + +const source = `query GetUser($id: ID!) { + user(id: $id) @skip(if: false) { + name + bio # a comment + note(text: "# is not a comment") + } +}`; +const html = highlightGraphQL(source); +assert.match(html, /graphql-keyword/); +assert.match(html, /graphql-variable/); +assert.match(html, /graphql-directive/); +assert.match(html, /graphql-literal/); +assert.match(html, /graphql-comment/); +assert.match(html, /graphql-string/); +assert.doesNotMatch(html, /graphql-comment[^>]*>[^<]*# is not a comment/); +const escaped = highlightGraphQL(''); +assert.match(escaped, /</); +assert.doesNotMatch(escaped, / + HTML + html = html.sub(/]*>/i, "")&.strip + title || file.page_name.to_s.sub(/_md\z/, "").tr("_", " ").split.map(&:capitalize).join(" ") + end + end + end +end diff --git a/tool/docs/guide_audit.rb b/tool/docs/guide_audit.rb new file mode 100644 index 00000000000..ae95354ebb6 --- /dev/null +++ b/tool/docs/guide_audit.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +require "optparse" +require "yaml" + +module GraphQLDocs + # Verifies that API-specific guides have moved into source comments and that + # every other guide remains an explicitly classified standalone page. + class GuideAudit + MANIFEST = "docs/guide_classification.yml" + GENERATED_PREFIX = "guides/yardoc/" + + def initialize(root: Dir.pwd) + @root = File.expand_path(root) + @manifest = YAML.load_file(File.join(@root, MANIFEST)) + end + + def check + errors = [] + guides = markdown_guides + api_entries = @manifest.fetch("api_comments") + standalone = @manifest.fetch("standalone") + policy = @manifest.fetch("standalone_policy") + errors << "standalone policy must explain the classification" if policy.to_s.strip.empty? + + api_paths = api_entries.map { |entry| entry.fetch("guide") } + generated = generated_guides + classified = api_paths + standalone + generated + + errors << "duplicate guide classification" unless classified.uniq.length == classified.length + missing = guides - classified + errors.concat(missing.map { |path| "#{path}: missing classification" }) + unknown = classified - guides + errors.concat(unknown.map { |path| "#{path}: does not exist" }) + + api_entries.each do |entry| + check_api_entry(entry, errors) + end + + errors.each { |error| warn error } + errors + end + + private + + def markdown_guides + Dir[File.join(@root, "guides", "**", "*.md")].map do |path| + path.delete_prefix("#{@root}/") + end.sort + end + + def generated_guides + Array(@manifest["generated_api"]).flat_map do |pattern| + Dir[File.join(@root, pattern)].map { |path| path.delete_prefix("#{@root}/") } + end.sort + end + + def check_api_entry(entry, errors) + guide = entry.fetch("guide") + source = entry.fetch("source") + constant = entry.fetch("constant") + guide_path = File.join(@root, guide) + source_path = File.join(@root, source) + + errors << "#{guide}: source is missing" unless File.file?(source_path) + max_lines = entry.fetch("max_lines", 50) + errors << "#{guide}: guide exceeds its documented migration size" if File.readlines(guide_path).length > max_lines + + if entry["kind"] == "hybrid" && entry.fetch("rationale", "").to_s.strip.empty? + errors << "#{guide}: hybrid API guide requires a rationale" + end + + unless File.file?(guide_path) && File.read(guide_path).include?("rdoc-ref:#{constant}") + errors << "#{guide}: entry point must link to #{constant}" + end + + return unless File.file?(source_path) + + source_text = File.read(source_path) + marker = "migrated from #{guide}" + errors << "#{source}: missing migration marker for #{guide}" unless source_text.include?(marker) + declaration = constant.split(/[.#]/, 2).first.split("::").last + errors << "#{source}: missing #{constant} declaration" unless source_text.include?(declaration) + + Array(entry["required_sections"]).each do |section| + errors << "#{source}: missing migrated section #{section.inspect}" unless source_text.include?(section) + end + end + end +end + +if __FILE__ == $PROGRAM_NAME + options = { root: Dir.pwd } + OptionParser.new do |parser| + parser.banner = "Usage: ruby tool/docs/guide_audit.rb [options]" + parser.on("--root PATH", "Repository root") { |path| options[:root] = path } + end.parse! + exit 1 unless GraphQLDocs::GuideAudit.new(root: options.fetch(:root)).check.empty? +end diff --git a/tool/docs/legacy.rb b/tool/docs/legacy.rb new file mode 100644 index 00000000000..65fc18a0b03 --- /dev/null +++ b/tool/docs/legacy.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require "json" +require "pathname" + +module GraphQLDocs + class LegacyAnchors + def self.install(root:, search_index: nil) + new(root, search_index: search_index).install + end + + def initialize(root, search_index: nil) + @root = Pathname.new(root).expand_path + @search_index = search_index || @root.join("js/search_data.js") + end + + def install + entries.each do |entry| + next unless ["instance_method", "class_method"].include?(entry.fetch("type")) + + path, current_fragment = entry.fetch("path").split("#", 2) + next unless current_fragment + + page = @root.join(path) + next unless page.file? + + legacy_fragment = legacy_fragment(entry) + html = File.read(page) + anchors = [] + unless html.include?(%() + end + unless html.include?(%() + end + next if anchors.empty? + + insertion = "#{anchors.join("\n")}\n" + if html.include?(%(}, "#{insertion}") + end + File.write(page, html) + end + end + + private + + def entries + data = File.read(@search_index).strip.delete_prefix("var search_data = ").delete_suffix(";") + JSON.parse(data).fetch("index") + end + + def legacy_fragment(entry) + name = entry.fetch("full_name") + method_name = entry.fetch("type") == "instance_method" ? name.split("#", 2).last : name.split(".").last + "#{method_name}-#{entry.fetch("type")}" + end + end +end diff --git a/tool/docs/link_checker.rb b/tool/docs/link_checker.rb new file mode 100644 index 00000000000..2be98f4bcd8 --- /dev/null +++ b/tool/docs/link_checker.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +require "cgi" +require "json" +require "find" +require "optparse" +require "set" +require "uri" + +module GraphQLDocs + class LinkChecker + LINK_PATTERN = /(?:href|src)\s*=\s*["']([^"']+)["']/i.freeze + FRAGMENT_PATTERN = /(?:id|name)\s*=\s*["']([^"']+)["']/i.freeze + EXTERNAL_SCHEMES = ["data", "file", "http", "https", "javascript", "mailto"].freeze + + def initialize(root, allow_root_links: false, root_links: nil) + @root = File.expand_path(root) + @allow_root_links = allow_root_links + @root_links = root_links && File.expand_path(root_links) + @files = Find.find(@root).select { |path| File.file?(path) }.to_set + end + + def check + missing = [] + html_files.each do |source| + File.read(source, encoding: "UTF-8").scan(LINK_PATTERN).flatten.each do |raw_target| + target, fragment = split_target(raw_target) + next if external?(target) + if target.start_with?("/") && @allow_root_links + if @root_links + destination = resolve_root_link(target) + unless destination && File.file?(destination) + missing << [source, raw_target, "file"] + next + end + if fragment && !fragment_present?(destination, fragment) + missing << [source, raw_target, "fragment"] + end + end + next + end + next unless document_link?(target, fragment) + + destination = resolve(source, target) + unless File.file?(destination) + missing << [source, raw_target, "file"] + next + end + next unless fragment + next if fragment_present?(destination, fragment) + + missing << [source, raw_target, "fragment"] + end + end + report(missing) + missing + end + + private + + def html_files + @files.select { |path| path.end_with?(".html") } + end + + def split_target(raw_target) + target, fragment = raw_target.split("#", 2) + target ||= "" + [target.to_s.split("?", 2).first.to_s, fragment && CGI.unescape(fragment)] + end + + def external?(target) + target.start_with?("#") || EXTERNAL_SCHEMES.include?(URI.parse(target).scheme) + rescue URI::InvalidURIError + false + end + + def document_link?(target, fragment) + fragment || target.start_with?("/") || target.end_with?(".html") + end + + def resolve(source, target) + return source if target.empty? + + path = if target.start_with?("/") + File.join(@root, target.delete_prefix("/")) + else + File.expand_path(target, File.dirname(source)) + end + path = File.join(path, "index.html") if File.directory?(path) + path + end + + def resolve_root_link(target) + path = File.join(@root_links, target.delete_prefix("/")) + candidates = [path, "#{path}.html", File.join(path, "index.html")] + candidates.find { |candidate| File.file?(candidate) } + end + + def fragments(path) + File.read(path, encoding: "UTF-8").scan(FRAGMENT_PATTERN).flatten.to_set + end + + def fragment_present?(path, fragment, visited = Set.new) + available = fragments(path) + return true if available.include?(fragment) + + normalized = fragment.gsub(/-+/, "-") + return true if available.any? { |candidate| candidate.gsub(/-+/, "-") == normalized } + + return false if visited.include?(path) + + visited << path + redirect = File.read(path, encoding: "UTF-8")[/]+\burl=["']?([^"'\s>]+)/i, 1] + redirect && fragment_present?(resolve(path, redirect), fragment, visited) + end + + def report(missing) + missing.each { |source, target, kind| warn "#{kind}: #{source.delete_prefix("#{@root}/")} -> #{target}" } + puts "Checked #{html_files.size} HTML files; #{missing.size} broken links" + end + end +end + +if __FILE__ == $PROGRAM_NAME + options = {} + OptionParser.new do |parser| + parser.banner = "Usage: ruby tool/docs/link_checker.rb --root PATH" + parser.on("--root PATH", "Generated documentation root") { |path| options[:root] = path } + parser.on("--json PATH", "Write broken links as JSON") { |path| options[:json] = path } + parser.on("--allow-root-links", "Skip links to the published site's root") { options[:allow_root_links] = true } + parser.on("--root-links PATH", "Published site root used to validate absolute links") { |path| options[:root_links] = path } + parser.on("--strict", "Fail when broken links are found") { options[:strict] = true } + end.parse! + + abort "--root is required" unless options[:root] + missing = GraphQLDocs::LinkChecker.new(options[:root], allow_root_links: options[:allow_root_links], root_links: options[:root_links]).check + File.write(options[:json], JSON.pretty_generate(missing.map { |source, target, kind| { "source" => source, "target" => target, "kind" => kind } }) + "\n") if options[:json] + exit 1 if options[:strict] && !missing.empty? +end diff --git a/tool/docs/migrate_guides.rb b/tool/docs/migrate_guides.rb new file mode 100644 index 00000000000..1858d188eec --- /dev/null +++ b/tool/docs/migrate_guides.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require "optparse" +require "uri" + +module GraphQLDocs + class GuideMigrator + FRONT_MATTER = /\A---\s*\n(.*?)\n---\s*\n/m.freeze + API_DOC = /\{\{\s*["']([^"']+)["']\s*\|\s*api_doc\s*\}\}/.freeze + PLAIN_REFERENCE = /\{\{\s*["']([^"']+)["']\s*\}\}/.freeze + SITE_BASE_URL = /\{\{\s*site\.base_url\s*\}\}/.freeze + INTERNAL_LINK = /\{%\s*internal_link\s+["']([^"']+)["']\s*,\s*["']([^"']+)["']\s*,?\s*%\}/.freeze + IMAGE = /\{\{\s*["']([^"']+)["']\s*\|\s*link_to_img\s*:\s*["']([^"']+)["']\s*\}\}/.freeze + OPEN_ISSUE = /\{%\s*open_an_issue\s+["']([^"']+)["'](?:\s*,\s*["']([^"']*)["'])?\s*%\}/.freeze + CALLOUT = /\{%\s*callout\s+(\w+)\s*%\}\s*\n(.*?)\{%\s*endcallout\s*%\}/m.freeze + GUIDE_LINK = /\]\((?![\/#]|https?:|mailto:|rdoc-ref:)([A-Za-z][\w-]*(?:\/[A-Za-z0-9_.-]+)*(?:#[^)]+)?)\)/.freeze + RDOC_REFERENCE = /rdoc-ref:([A-Za-z][\w:#.?!-]*)/.freeze + GUIDE_ROOTS = [ + "authorization", "changesets", "dataloader", "defer", "development", "errors", "execution", "faq", "fields", + "getting_started", "javascript_client", "language_tools", "limiters", "mutations", "object_cache", + "operation_store", "pagination", "pro", "queries", "related_projects", "relay", "schema", "subscriptions", + "testing", "type_definitions", + ].freeze + + attr_reader :paths + + def initialize(root: Dir.pwd, paths: nil) + @root = File.expand_path(root) + @paths = paths || Dir[File.join(@root, "guides", "**", "*.md")].sort + end + + def run(write: false) + results = @paths.map { |path| [path, migrate(File.read(path))] } + results.each { |path, content| File.write(path, content) if write && content != File.read(path) } + results + end + + def migrate(content) + title = content[FRONT_MATTER, 1]&.then { |front| front[/^title:\s*["']?([^"'\n]+)["']?\s*$/i, 1] }&.strip + content = content.sub(FRONT_MATTER, title ? "# #{title}\n\n" : "") + content = content.gsub(API_DOC) { api_link(Regexp.last_match(1)) } + content = content.gsub(PLAIN_REFERENCE) { api_link(Regexp.last_match(1)) } + content = content.gsub(SITE_BASE_URL, "") + content = content.gsub(INTERNAL_LINK) { "[#{Regexp.last_match(1)}](#{Regexp.last_match(2)})" } + content = content.gsub(IMAGE) { "![#{Regexp.last_match(2)}](#{Regexp.last_match(1)})" } + content = content.gsub(OPEN_ISSUE) { issue_link(Regexp.last_match(1), Regexp.last_match(2)) } + content = content.gsub(CALLOUT) do + heading = Regexp.last_match(1).capitalize + body = Regexp.last_match(2).lines.map { |line| line.strip.empty? ? ">" : "> #{line.rstrip}" }.join("\n") + "> **#{heading}:**\n>\n#{body}\n" + end + content = normalize_guide_links(content) + content.sub(/\n+\z/, "\n") + end + + private + + def api_link(reference) + reference = normalize_api_reference(reference) + "[#{reference}](rdoc-ref:#{reference})" + end + + def normalize_api_reference(reference) + return reference if reference.start_with?("GraphQL::", "#") + + root = reference.split("::", 2).first.split(/[.#]/, 2).first + GUIDE_API_ROOTS.include?(root) ? "GraphQL::#{reference}" : reference + end + + def normalize_guide_links(content) + content = content.gsub(RDOC_REFERENCE) do + "rdoc-ref:#{normalize_api_reference(Regexp.last_match(1))}" + end + content.gsub(GUIDE_LINK) do + target = Regexp.last_match(1) + root = target.split("/", 2).first.split("#", 2).first + GUIDE_ROOTS.include?(root) ? "](/#{target})" : "](#{target})" + end + end + + GUIDE_API_ROOTS = [ + "Analysis", "Authorization", "Dataloader", "Defer", "Error", "Execution", "Field", "Language", "Limiters", "Mutation", + "ObjectCache", "Pagination", "Parser", "Query", "Relay", "Schema", "Subscriptions", "Testing", "Trace", "Tracing", "Types", + ].freeze + + def issue_link(title, body) + params = URI.encode_www_form("title" => title, "body" => body.to_s) + "[open an issue](https://github.com/rmosolgo/graphql-ruby/issues/new?#{params})" + end + end +end + +if __FILE__ == $PROGRAM_NAME + options = { root: Dir.pwd, write: false } + OptionParser.new do |parser| + parser.banner = "Usage: ruby tool/docs/migrate_guides.rb [options] [files...]" + parser.on("--write", "Write converted guides") { options[:write] = true } + parser.on("--check", "Fail when a guide needs conversion") { options[:check] = true } + parser.on("--root PATH", "Repository root") { |path| options[:root] = path } + end.parse! + paths = ARGV.empty? ? nil : ARGV.map { |path| File.expand_path(path, options.fetch(:root)) } + results = GraphQLDocs::GuideMigrator.new(root: options.fetch(:root), paths: paths).run(write: options.fetch(:write)) + changed = results.select { |path, content| content != File.read(path) }.map(&:first) + changed.each { |path| puts "convert: #{path}" } + exit 1 if options[:check] && changed.any? +end diff --git a/tool/docs/publish_check.rb b/tool/docs/publish_check.rb new file mode 100644 index 00000000000..a6d4406a324 --- /dev/null +++ b/tool/docs/publish_check.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require "digest" +require "fileutils" +require "json" +require "optparse" + +module GraphQLDocs + class PublishCheck + def initialize(pages:) + @pages = File.expand_path(pages) + @api_docs = File.join(@pages, "api-doc") + end + + def snapshot + files = api_doc_files.to_h do |path| + [relative(path), Digest::SHA256.file(path).hexdigest] + end + { "files" => files } + end + + def verify(snapshot:, allowed_version: nil, expected_version: nil) + before = snapshot.fetch("files") + after = snapshot().fetch("files") + changed = (before.keys | after.keys).select { |path| before[path] != after[path] } + allowed_prefix = allowed_version && "api-doc/#{allowed_version}/" + unexpected = changed.reject { |path| allowed_prefix && path.start_with?(allowed_prefix) } + errors = [] + errors << "existing api-doc files changed outside #{allowed_prefix}" unless unexpected.empty? + if expected_version + prefix = "api-doc/#{expected_version}/" + errors << "versioned API docs are missing" unless after.keys.any? { |path| path.start_with?(prefix) } + end + [errors, changed] + end + + private + + def api_doc_files + return [] unless Dir.exist?(@api_docs) + + Dir[File.join(@api_docs, "**", "*")].select { |path| File.file?(path) } + end + + def relative(path) + path.delete_prefix("#{@pages}/") + end + end +end + +if __FILE__ == $PROGRAM_NAME + options = { mode: nil } + parser = OptionParser.new do |p| + p.banner = "Usage: ruby tool/docs/publish_check.rb snapshot|verify [options]" + p.on("--pages PATH", "Checked-out GitHub Pages root") { |path| options[:pages] = path } + p.on("--snapshot PATH", "Snapshot JSON path") { |path| options[:snapshot] = path } + p.on("--allow-version VERSION", "Allow changes under api-doc/VERSION") { |version| options[:allow_version] = version } + p.on("--expected-version VERSION", "Require api-doc/VERSION after publishing") { |version| options[:expected_version] = version } + end + options[:mode] = ARGV.shift + parser.parse! + abort "mode must be snapshot or verify" unless ["snapshot", "verify"].include?(options[:mode]) + abort "--pages is required" unless options[:pages] + checker = GraphQLDocs::PublishCheck.new(pages: options.fetch(:pages)) + if options[:mode] == "snapshot" + abort "--snapshot is required" unless options[:snapshot] + File.write(options.fetch(:snapshot), JSON.pretty_generate(checker.snapshot) + "\n") + puts "Snapshotted #{checker.snapshot.fetch("files").size} API documentation files" + else + abort "--snapshot is required" unless options[:snapshot] + snapshot = JSON.parse(File.read(options.fetch(:snapshot))) + errors, changed = checker.verify( + snapshot: snapshot, + allowed_version: options[:allow_version], + expected_version: options[:expected_version], + ) + errors.each { |error| warn error } + puts "Verified publication; #{changed.size} API documentation files changed" + exit 1 unless errors.empty? + end +end diff --git a/tool/docs/rdoc_ref_checker.rb b/tool/docs/rdoc_ref_checker.rb new file mode 100644 index 00000000000..0bcd38f63ea --- /dev/null +++ b/tool/docs/rdoc_ref_checker.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require "optparse" +require "json" + +module GraphQLDocs + # RDoc treats `rdoc-ref:` as a link target while it is rendering Markdown. + # It must not leak into the published HTML: a leaked reference is visible to + # readers and cannot be followed by browsers or the site link checker. + class RDocReferenceChecker + REFERENCE = /rdoc-ref:[A-Za-z][A-Za-z0-9_:#.?!-]*/.freeze + + def initialize(root) + @root = File.expand_path(root) + end + + def check + unresolved = [] + published_files.each do |path| + File.read(path, encoding: "UTF-8").scan(REFERENCE).uniq.each do |reference| + unresolved << { + "file" => path.delete_prefix("#{@root}/"), + "reference" => reference, + } + end + end + unresolved + end + + def report(unresolved) + unresolved.each do |entry| + warn "unresolved RDoc reference: #{entry.fetch("file")} -> #{entry.fetch("reference")}" + end + puts "Checked #{published_files.size} published files; #{unresolved.size} unresolved RDoc references" + end + + private + + def published_files + Dir[File.join(@root, "**", "*.html"), File.join(@root, "js", "search_data.js")].select do |path| + File.file?(path) + end + end + end +end + +if __FILE__ == $PROGRAM_NAME + options = {} + OptionParser.new do |parser| + parser.banner = "Usage: ruby tool/docs/rdoc_ref_checker.rb --root PATH" + parser.on("--root PATH", "Generated documentation root") { |path| options[:root] = path } + parser.on("--json PATH", "Write unresolved references as JSON") { |path| options[:json] = path } + end.parse! + + abort "--root is required" unless options[:root] + checker = GraphQLDocs::RDocReferenceChecker.new(options.fetch(:root)) + unresolved = checker.check + checker.report(unresolved) + File.write(options.fetch(:json), JSON.pretty_generate(unresolved) + "\n") if options[:json] + exit 1 unless unresolved.empty? +end diff --git a/tool/docs/redirects.rb b/tool/docs/redirects.rb new file mode 100644 index 00000000000..c856cd5e66e --- /dev/null +++ b/tool/docs/redirects.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require "cgi" +require "fileutils" +require "pathname" +require "yaml" +require_relative "legacy" + +module GraphQLDocs + class Redirects + def self.generate(root:, output:, manifest: nil) + new(root: root, output: output, manifest: manifest).generate + end + + def initialize(root:, output:, manifest: nil) + @root = Pathname.new(root).expand_path + @output = Pathname.new(output).expand_path + @manifest = Pathname.new(manifest || @root.join("docs/redirects.yml")).expand_path + end + + def generate + redirects.each do |entry| + destination = destination_for(entry.fetch("destination")) + write_redirect(entry.fetch("old_path"), destination) + end + redirects.size + end + + def redirects + config = YAML.safe_load(File.read(@manifest), permitted_classes: [], aliases: false) || {} + explicit = Array(config.fetch("redirects", [])) + guides = if config["guide_source"] + Dir[File.join(@root, config.fetch("guide_source"), "**", "*.md")].filter_map do |path| + relative = Pathname.new(path).relative_path_from(@root).to_s + next if relative.split("/").any? { |part| part.start_with?("_") || part == "yardoc" } + + source_prefix = "#{config.fetch("guide_source").delete_suffix("/")}/" + old_relative = relative.delete_prefix(source_prefix) + old_path = "/#{old_relative.delete_suffix(".md")}" + { "old_path" => old_path, "destination" => { "kind" => "page", "value" => relative } } + end + else + [] + end + (explicit + guides).uniq { |entry| entry.fetch("old_path") } + end + + private + + def destination_for(destination) + case destination.fetch("kind") + when "page" + value = destination.fetch("value") + value = value.sub(/\.md\z/, "_md.html") + "/#{value.delete_prefix("/")}" + when "rdoc_ref" + resolve_rdoc_ref(destination.fetch("value")) + else + raise ArgumentError, "Unsupported redirect destination: #{destination.inspect}" + end + end + + def resolve_rdoc_ref(reference) + data = File.read(@output.join("js/search_data.js")).strip.delete_prefix("var search_data = ").delete_suffix(";") + entry = JSON.parse(data).fetch("index").find do |candidate| + candidate["full_name"] == reference || candidate["full_name"]&.sub(/::([^:]+)\z/, '.\\1') == reference + end + raise ArgumentError, "No RDoc search entry for #{reference}" unless entry + + "/#{entry.fetch("path")}" + end + + def write_redirect(old_path, destination) + relative = old_path.delete_prefix("/") + locations = [@output.join(relative, "index.html")] + locations << @output.join("#{relative}.html") unless relative.end_with?(".html") + locations.each do |path| + FileUtils.mkdir_p(path.dirname) + href = relative_url(path, destination) + escaped_destination = CGI.escapeHTML(destination) + html = <<~HTML + + + + + + + Redirecting to GraphQL Ruby documentation + + +

This page moved to #{escaped_destination}.

+ + + + HTML + File.write(path, html) + end + end + + def relative_url(path, destination) + @output.join(destination.delete_prefix("/")).relative_path_from(path.dirname).to_s + end + end +end diff --git a/tool/docs/type_signatures.rb b/tool/docs/type_signatures.rb new file mode 100644 index 00000000000..d79f9bcee16 --- /dev/null +++ b/tool/docs/type_signatures.rb @@ -0,0 +1,260 @@ +# frozen_string_literal: true + +require "optparse" + +module GraphQLDocs + class TypeSignatureMigrator + SECTION_NAMES = ["Parameters", "Options", "Returns", "Yields", "Attributes"].freeze + SECTION = /^\s*\*\*(#{SECTION_NAMES.join("|")})\*\*\s*$/.freeze + ENTRY = /^\s*-\s+`([^`]+)`(?:\s+\(`([^`]+)`\))?/.freeze + METHOD = /\bdef\s+(?:self\.)?([^\s(]+)(?:\((.*?)\))?/m.freeze + ATTRIBUTE = /\battr_(?:reader|writer|accessor)\s+(.+)/.freeze + + attr_reader :paths + + def initialize(root: Dir.pwd, paths: nil) + @root = File.expand_path(root) + @paths = paths || Dir[File.join(@root, "lib", "**", "*.rb")].sort + end + + def run(write: false) + @paths.map do |path| + original = File.read(path) + converted = migrate(original) + File.write(path, converted) if write && converted != original + [path, original != converted] + end + end + + def migrate(source) + lines = source.lines + output = [] + index = 0 + while index < lines.length + unless comment_line?(lines[index]) + output << lines[index] + index += 1 + next + end + + block_start = index + index += 1 while index < lines.length && comment_line?(lines[index]) + block = lines[block_start...index] + declaration = declaration_after(lines, index) + signature = signature_for(block, declaration) + output.concat(add_signature(block, signature)) + end + output.join + end + + private + + def comment_line?(line) + line.match?(/^\s*#(?:\s|$)/) + end + + def declaration_after(lines, index) + return "" unless index < lines.length + return "" unless lines[index].match?(/^\s*(?:def\b|attr_(?:reader|writer|accessor)\b)/) + + declaration = +"" + while index < lines.length && declaration.length < 2_000 + declaration << lines[index] + index += 1 + break if balanced_parentheses?(declaration) + end + declaration + end + + def balanced_parentheses?(text) + depth = 0 + text.each_char do |character| + depth += 1 if character == "(" + depth -= 1 if character == ")" + end + depth.zero? + end + + def signature_for(block, declaration) + return if declaration.empty? || declaration.include?(":nodoc:") || block.any? { |line| line.include?(":nodoc:") } + + sections = typed_sections(block) + return if sections.empty? + + if (method = declaration.match(METHOD)) + method_name = method[1] + parameters = method_parameters(declaration, method) + arguments = parameters.map { |parameter| format_parameter(parameter, sections) } + result_type = return_type(sections) + return "#{method_name}(#{arguments.join(", ")})#{result_type ? " -> #{result_type}" : ""}" + end + + return unless (attribute = declaration.match(ATTRIBUTE)) + name = attribute[1].split(",", 2).first.strip.sub(/\A:/, "") + type = sections.fetch("Returns", []).filter_map(&:first).first + return unless type + + "#{name} -> #{normalize_type(type)}" + end + + def typed_sections(block) + sections = Hash.new { |hash, key| hash[key] = [] } + section = nil + block.each do |line| + text = line.sub(/^\s*# ?/, "").chomp + if (match = text.match(SECTION)) + section = match[1] + elsif section && (entry = text.match(ENTRY)) + sections[section] << [entry[1], entry[2]] + end + end + sections.delete_if { |_name, entries| entries.empty? } + end + + def method_parameters(declaration, method) + params = method[2] + return [] unless params + + params = params.strip + return [] if params.empty? + + split_top_level(params).map do |parameter| + parameter = parameter.strip + name = parameter[/\A(?:\*{0,2}|&)?([a-zA-Z_]\w*[!?=]?|\.\.\.)/, 1] + {source: parameter, name: name} + end + end + + def split_top_level(text) + values = [] + start = 0 + depth = 0 + quote = nil + escaped = false + text.each_char.with_index do |character, index| + if quote + escaped = !escaped if character == "\\" && !escaped + quote = nil if character == quote && !escaped + escaped = false unless character == "\\" + elsif ["'", '"'].include?(character) + quote = character + elsif "([{<".include?(character) + depth += 1 + elsif ")]} >".delete(" ").include?(character) + depth -= 1 + elsif character == "," && depth.zero? + values << text[start...index] + start = index + 1 + end + end + values << text[start..] + end + + def format_parameter(parameter, sections) + name = parameter[:name] + return parameter[:source] unless name + + entry = sections.values.flatten(1).find { |candidate| candidate[0] == name } + type = entry && entry[1] + + prefix = parameter[:source].start_with?("**") ? "**" : parameter[:source].start_with?("*") ? "*" : parameter[:source].start_with?("&") ? "&" : "" + keyword = parameter[:source].include?(":") && !parameter[:source].include?("=>") ? ":" : "" + parameter_name = "#{prefix}#{name}#{keyword}" + return parameter_name unless type + + "#{normalize_type(type)} #{parameter_name}" + end + + def return_type(sections) + types = sections.fetch("Returns", []).filter_map(&:first).uniq + return if types.empty? + + types.map { |type| normalize_type(type) }.join(" | ") + end + + def normalize_type(type) + type = type.to_s.strip + type = type.gsub(/\A<|>\z/, "") if type.start_with?("<") && type.end_with?(">") + return type if type.start_with?("#") + + type = type.gsub(/\bBoolean\b/, "bool") + type = normalize_generic_types(type) + split_top_level(type).map(&:strip).reject(&:empty?).join(" | ") + end + + def normalize_generic_types(type) + loop do + changed = false + protected_type = type.gsub("=>", "\0") + protected_type = protected_type.gsub(/\b([A-Z][A-Za-z0-9_:]*)<([^<>]*)>/) do + changed = true + generic_name = Regexp.last_match(1) + generic_contents = Regexp.last_match(2).gsub("\0", "=>") + if generic_name == "Hash" + hash_contents = generic_contents.split("=>", 2).map(&:strip) + hash_contents = split_top_level(generic_contents) if hash_contents.length == 1 + if hash_contents.length == 2 + "Hash[#{normalize_type(hash_contents[0])}, #{normalize_type(hash_contents[1])}]" + else + "Hash[#{normalize_type(generic_contents)}]" + end + else + "#{generic_name}[#{normalize_type(generic_contents)}]" + end + end + type = protected_type.gsub("\0", "=>") + type = type.gsub(/\bHash\{([^{}]*)\}/) do + changed = true + generic_contents = Regexp.last_match(1) + hash_contents = generic_contents.split("=>", 2).map(&:strip) + hash_contents = split_top_level(generic_contents) if hash_contents.length == 1 + if hash_contents.length == 2 + "Hash[#{normalize_type(hash_contents[0])}, #{normalize_type(hash_contents[1])}]" + else + "Hash[#{normalize_type(generic_contents)}]" + end + end + break unless changed + end + type + end + + def add_signature(block, signature) + return block unless signature + + indent = block.first[/\A\s*/] + marker_index = block.index { |line| line.include?(":call-seq:") } + if marker_index + signature_index = marker_index + 1 + updated = block.dup + if signature_index < updated.length && updated[signature_index].match?(/^\s*#\s+/) + updated[signature_index] = "#{indent}# #{signature}\n" + else + updated.insert(signature_index, "#{indent}# #{signature}\n") + end + return updated + end + + insertion = [ + "#{indent}#\n", + "#{indent}# :call-seq:\n", + "#{indent}# #{signature}\n", + ] + block + insertion + end + end +end + +if __FILE__ == $PROGRAM_NAME + options = {root: Dir.pwd, write: false} + OptionParser.new do |parser| + parser.banner = "Usage: ruby tool/docs/type_signatures.rb [options]" + parser.on("--write", "Write RDoc call-seq signatures") { options[:write] = true } + parser.on("--check", "Fail when a signature is missing") { options[:check] = true } + parser.on("--root PATH", "Repository root") { |path| options[:root] = path } + end.parse! + + results = GraphQLDocs::TypeSignatureMigrator.new(root: options[:root]).run(write: options[:write]) + results.select(&:last).each { |path, _changed| puts "convert: #{path}" } + exit 1 if options[:check] && results.any?(&:last) +end diff --git a/tool/docs/version_check.rb b/tool/docs/version_check.rb new file mode 100644 index 00000000000..b49db20df54 --- /dev/null +++ b/tool/docs/version_check.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "json" +require "optparse" + +module GraphQLDocs + class VersionCheck + def initialize(root:, version:) + @root = File.expand_path(root) + @version = version + end + + def check + errors = [] + errors << "missing index.html" unless File.file?(File.join(@root, "index.html")) + index_path = File.join(@root, "js", "search_data.js") + errors << "missing js/search_data.js" unless File.file?(index_path) + if File.file?(index_path) + data = File.read(index_path).strip.delete_prefix("var search_data = ").delete_suffix(";") + begin + index = JSON.parse(data).fetch("index") + errors << "search index is empty" if index.empty? + rescue JSON::ParserError, KeyError => error + errors << "invalid search index: #{error.message}" + end + end + errors << "versioned output contains unresolved RDoc references" if Dir[File.join(@root, "**", "*.html")].any? do |path| + File.read(path, encoding: "UTF-8").include?("rdoc-ref:") + end + errors + end + end +end + +if __FILE__ == $PROGRAM_NAME + options = {} + OptionParser.new do |parser| + parser.banner = "Usage: ruby tool/docs/version_check.rb --root PATH --version VERSION" + parser.on("--root PATH", "Versioned documentation root") { |path| options[:root] = path } + parser.on("--version VERSION", "Expected GraphQL-Ruby version") { |version| options[:version] = version } + end.parse! + abort "--root and --version are required" unless options[:root] && options[:version] + errors = GraphQLDocs::VersionCheck.new(root: options.fetch(:root), version: options.fetch(:version)).check + errors.each { |error| warn "#{options.fetch(:version)}: #{error}" } + puts "Validated versioned API docs for #{options.fetch(:version)}" if errors.empty? + exit 1 unless errors.empty? +end