Skip to content

Commit 0557e1e

Browse files
committed
Compiler for GraphQL::Schema
Which creates a strongly-typed sig for `#context`, if the context class was customized via the `context_class` DSL.
1 parent b63ae95 commit 0557e1e

File tree

4 files changed

+201
-0
lines changed

4 files changed

+201
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
begin
5+
gem("graphql", ">= 1.13")
6+
require "graphql"
7+
rescue LoadError
8+
return
9+
end
10+
11+
require "tapioca/dsl/helpers/graphql_type_helper"
12+
13+
module Tapioca
14+
module Dsl
15+
module Compilers
16+
# `Tapioca::Dsl::Compilers::GraphqlSchema` generates RBI files for subclasses of
17+
# [`GraphQL::Schema`](https://graphql-ruby.org/api-doc/2.1.7/GraphQL/Schema).
18+
#
19+
# For example, with the following `GraphQL::Schema` subclass:
20+
#
21+
# ~~~rb
22+
# class MySchema> < GraphQL::Schema
23+
# class MyContext < GraphQL::Query::Context; end
24+
#
25+
# context_class MyContext
26+
#
27+
# # ...
28+
# end
29+
# ~~~
30+
#
31+
# this compiler will produce the RBI file `my_schema.rbi` with the following content:
32+
#
33+
# ~~~rbi
34+
# # my_schema.rbi
35+
# # typed: true
36+
# class MySchema
37+
# sig { returns(MySchema::MyContext) }
38+
# def context; end
39+
# end
40+
# ~~~
41+
class GraphqlSchema < Compiler
42+
extend T::Sig
43+
44+
ConstantType = type_member { { fixed: T.class_of(GraphQL::Schema) } }
45+
46+
sig { override.void }
47+
def decorate
48+
custom_context_class = constant.context_class
49+
# Skip decoration if the context class hasn't been customized
50+
return if custom_context_class == GraphQL::Query::Context
51+
52+
return if constant.method_defined?(:context, false) # Skip if the Schema overrides the `#context` getter.
53+
54+
root.create_path(constant) do |schema|
55+
schema.create_method("context", return_type: T.must(name_of(custom_context_class)))
56+
end
57+
end
58+
59+
class << self
60+
extend T::Sig
61+
62+
sig { override.returns(T::Enumerable[Module]) }
63+
def gather_constants
64+
all_classes.select { |c| c < GraphQL::Schema && c != GraphQL::Query::NullContext::NullSchema }
65+
end
66+
end
67+
end
68+
end
69+
end
70+
end

manual/compiler_graphqlschema.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## GraphqlSchema
2+
3+
`Tapioca::Dsl::Compilers::GraphqlSchema` generates RBI files for subclasses of
4+
[`GraphQL::Schema`](https://graphql-ruby.org/api-doc/2.1.7/GraphQL/Schema).
5+
6+
For example, with the following `GraphQL::Schema` subclass:
7+
8+
~~~rb
9+
class MySchema> < GraphQL::Schema
10+
class MyContext < GraphQL::Query::Context; end
11+
12+
context_class MyContext
13+
14+
# ...
15+
end
16+
~~~
17+
18+
this compiler will produce the RBI file `my_schema.rbi` with the following content:
19+
20+
~~~rbi
21+
# my_schema.rbi
22+
# typed: true
23+
class MySchema
24+
sig { returns(MySchema::MyContext) }
25+
def context; end
26+
end
27+
~~~

manual/compilers.md

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ In the following section you will find all available DSL compilers:
2828
* [FrozenRecord](compiler_frozenrecord.md)
2929
* [GraphqlInputObject](compiler_graphqlinputobject.md)
3030
* [GraphqlMutation](compiler_graphqlmutation.md)
31+
* [GraphqlSchema](compiler_graphqlschema.md)
3132
* [IdentityCache](compiler_identitycache.md)
3233
* [JsonApiClientResource](compiler_jsonapiclientresource.md)
3334
* [Kredis](compiler_kredis.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
require "spec_helper"
5+
6+
module Tapioca
7+
module Dsl
8+
module Compilers
9+
class GraphqlSchemaSpec < ::DslSpec
10+
describe "Tapioca::Dsl::Compilers::GraphqlSchema" do
11+
describe "initialize" do
12+
it "gathers no constants if there are no GraphQL::Schema subclasses" do
13+
assert_empty(gathered_constants)
14+
end
15+
16+
it "gathers only GraphQL::Schema subclasses" do
17+
add_ruby_file("content.rb", <<~RUBY)
18+
class MySchema < GraphQL::Schema
19+
end
20+
21+
class User
22+
end
23+
RUBY
24+
25+
assert_equal(["MySchema"], gathered_constants)
26+
end
27+
28+
it "gathers subclasses of GraphQL::Schema subclasses" do
29+
add_ruby_file("content.rb", <<~RUBY)
30+
class MyBaseSchema < GraphQL::Schema
31+
end
32+
33+
class MySchema < MyBaseSchema
34+
end
35+
RUBY
36+
37+
assert_equal(["MyBaseSchema", "MySchema"], gathered_constants)
38+
end
39+
end
40+
41+
describe "decorate" do
42+
it "generates an empty RBI file if there is no custom context_class is set" do
43+
add_ruby_file("create_comment.rb", <<~RUBY)
44+
class MySchema < GraphQL::Schema
45+
end
46+
RUBY
47+
48+
expected = <<~RBI
49+
# typed: strong
50+
RBI
51+
52+
assert_equal(expected, rbi_for(:MySchema))
53+
end
54+
55+
it "generates correct RBI file for subclass that sets context_class" do
56+
add_ruby_file("create_comment.rb", <<~RUBY)
57+
class MySchema < GraphQL::Schema
58+
class MyContext < GraphQL::Query::Context; end
59+
60+
context_class MyContext
61+
end
62+
RUBY
63+
64+
expected = <<~RBI
65+
# typed: strong
66+
67+
class MySchema
68+
sig { returns(MySchema::MyContext) }
69+
def context; end
70+
end
71+
RBI
72+
73+
assert_equal(expected, rbi_for(:MySchema))
74+
end
75+
76+
it "generates an empty RBI file if there is an inline signature" do
77+
add_ruby_file("create_comment.rb", <<~RUBY)
78+
class MySchema < GraphQL::Schema
79+
extend T::Sig
80+
81+
class MyContext < GraphQL::Query::Context; end
82+
83+
context_class MyContext
84+
85+
sig { returns(SomethingElse) }
86+
def context
87+
# ...
88+
end
89+
end
90+
RUBY
91+
92+
expected = <<~RBI
93+
# typed: strong
94+
RBI
95+
96+
assert_equal(expected, rbi_for(:MySchema))
97+
end
98+
end
99+
end
100+
end
101+
end
102+
end
103+
end

0 commit comments

Comments
 (0)