Skip to content

Commit

Permalink
Compiler for GraphQL::Schema
Browse files Browse the repository at this point in the history
Which creates a strongly-typed sig for `#context`, if the context class was customized via the `context_class`  DSL.
  • Loading branch information
amomchilov committed Dec 15, 2023
1 parent 71b495d commit c0216ac
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 0 deletions.
70 changes: 70 additions & 0 deletions lib/tapioca/dsl/compilers/graphql_schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# typed: strict
# frozen_string_literal: true

begin
gem("graphql", ">= 1.13")
require "graphql"
rescue LoadError
return
end

require "tapioca/dsl/helpers/graphql_type_helper"

module Tapioca
module Dsl
module Compilers
# `Tapioca::Dsl::Compilers::GraphqlSchema` generates RBI files for subclasses of
# [`GraphQL::Schema`](https://graphql-ruby.org/api-doc/2.1.7/GraphQL/Schema).
#
# For example, with the following `GraphQL::Schema` subclass:
#
# ~~~rb
# class MySchema> < GraphQL::Schema
# class MyContext < GraphQL::Query::Context; end
#
# context_class MyContext
#
# # ...
# end
# ~~~
#
# this compiler will produce the RBI file `my_schema.rbi` with the following content:
#
# ~~~rbi
# # my_schema.rbi
# # typed: true
# class MySchema
# sig { returns(MySchema::MyContext) }
# def context; end
# end
# ~~~
class GraphqlSchema < Compiler
extend T::Sig

ConstantType = type_member { { fixed: T.class_of(GraphQL::Schema) } }

sig { override.void }
def decorate
custom_context_class = constant.context_class
# Skip decoration if the context class hasn't been customized
return if custom_context_class == GraphQL::Query::Context

return if constant.method_defined?(:context, false) # Skip if the Schema overrides the `#context` getter.

root.create_path(constant) do |schema|
schema.create_method("context", return_type: T.must(name_of(custom_context_class)))
end
end

class << self
extend T::Sig

sig { override.returns(T::Enumerable[Module]) }
def gather_constants
all_classes.select { |c| c < GraphQL::Schema && c != GraphQL::Query::NullContext::NullSchema }
end
end
end
end
end
end
27 changes: 27 additions & 0 deletions manual/compiler_graphqlschema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## GraphqlSchema

`Tapioca::Dsl::Compilers::GraphqlSchema` generates RBI files for subclasses of
[`GraphQL::Schema`](https://graphql-ruby.org/api-doc/2.1.7/GraphQL/Schema).

For example, with the following `GraphQL::Schema` subclass:

~~~rb
class MySchema> < GraphQL::Schema
class MyContext < GraphQL::Query::Context; end

context_class MyContext

# ...
end
~~~

this compiler will produce the RBI file `my_schema.rbi` with the following content:

~~~rbi
# my_schema.rbi
# typed: true
class MySchema
sig { returns(MySchema::MyContext) }
def context; end
end
~~~
1 change: 1 addition & 0 deletions manual/compilers.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ In the following section you will find all available DSL compilers:
* [FrozenRecord](compiler_frozenrecord.md)
* [GraphqlInputObject](compiler_graphqlinputobject.md)
* [GraphqlMutation](compiler_graphqlmutation.md)
* [GraphqlSchema](compiler_graphqlschema.md)
* [IdentityCache](compiler_identitycache.md)
* [JsonApiClientResource](compiler_jsonapiclientresource.md)
* [Kredis](compiler_kredis.md)
Expand Down
103 changes: 103 additions & 0 deletions spec/tapioca/dsl/compilers/graphql_schema_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# typed: strict
# frozen_string_literal: true

require "spec_helper"

module Tapioca
module Dsl
module Compilers
class GraphqlSchemaSpec < ::DslSpec
describe "Tapioca::Dsl::Compilers::GraphqlSchema" do
describe "initialize" do
it "gathers no constants if there are no GraphQL::Schema subclasses" do
assert_empty(gathered_constants)
end

it "gathers only GraphQL::Schema subclasses" do
add_ruby_file("content.rb", <<~RUBY)
class MySchema < GraphQL::Schema
end
class User
end
RUBY

assert_equal(["MySchema"], gathered_constants)
end

it "gathers subclasses of GraphQL::Schema subclasses" do
add_ruby_file("content.rb", <<~RUBY)
class MyBaseSchema < GraphQL::Schema
end
class MySchema < MyBaseSchema
end
RUBY

assert_equal(["MyBaseSchema", "MySchema"], gathered_constants)
end
end

describe "decorate" do
it "generates an empty RBI file if there is no custom context_class is set" do
add_ruby_file("create_comment.rb", <<~RUBY)
class MySchema < GraphQL::Schema
end
RUBY

expected = <<~RBI
# typed: strong
RBI

assert_equal(expected, rbi_for(:MySchema))
end

it "generates correct RBI file for subclass that sets context_class" do
add_ruby_file("create_comment.rb", <<~RUBY)
class MySchema < GraphQL::Schema
class MyContext < GraphQL::Query::Context; end
context_class MyContext
end
RUBY

expected = <<~RBI
# typed: strong
class MySchema
sig { returns(MySchema::MyContext) }
def context; end
end
RBI

assert_equal(expected, rbi_for(:MySchema))
end

it "generates an empty RBI file if there is an inline signature" do
add_ruby_file("create_comment.rb", <<~RUBY)
class MySchema < GraphQL::Schema
extend T::Sig
class MyContext < GraphQL::Query::Context; end
context_class MyContext
sig { returns(SomethingElse) }
def context
# ...
end
end
RUBY

expected = <<~RBI
# typed: strong
RBI

assert_equal(expected, rbi_for(:MySchema))
end
end
end
end
end
end
end

0 comments on commit c0216ac

Please sign in to comment.