Skip to content
This repository was archived by the owner on Jul 25, 2024. It is now read-only.

Commit a06441f

Browse files
author
Josh Price
committed
Schema utils
1 parent 1e8b491 commit a06441f

File tree

2 files changed

+235
-0
lines changed

2 files changed

+235
-0
lines changed

lib/graphql/schema/generator.ex

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
defmodule GraphQL.Schema.Generator do
2+
3+
# TODO generate comments with the source types
4+
5+
def generate(base_filename, source_schema) do
6+
{:ok, ast} = GraphQL.Lang.Parser.parse(source_schema)
7+
module_name = base_filename |> Path.basename("_schema") |> Macro.camelize
8+
{:ok, generate_module(module_name, ast)}
9+
end
10+
11+
def generate_module(name, ast) do
12+
"""
13+
defmodule #{name}.Schema do
14+
#{walk_ast(ast)}
15+
end
16+
"""
17+
end
18+
19+
def walk_ast(doc = %{kind: :Document}) do
20+
"""
21+
alias GraphQL.Type
22+
23+
#{doc.definitions |> Enum.map(&walk_ast/1) |> Enum.join("\n")}
24+
25+
def schema do
26+
%GraphQL.Schema{
27+
query: Query.type,
28+
# mutation: Mutation.type
29+
}
30+
end
31+
"""
32+
end
33+
34+
def walk_ast(type_def = %{kind: :ObjectTypeDefinition}) do
35+
"""
36+
defmodule #{type_def.name.value} do
37+
def type do
38+
%Type.ObjectType{
39+
name: "#{type_def.name.value}",
40+
description: "#{type_def.name.value} description",
41+
fields: %{
42+
#{type_def.fields |> Enum.map(&walk_ast/1) |> Enum.join(",\n ")}
43+
}#{interfaces(type_def)}
44+
}
45+
end
46+
end
47+
"""
48+
end
49+
50+
def walk_ast(field = %{kind: :FieldDefinition, arguments: args}) when is_list(args) and length(args) > 0 do
51+
"""
52+
#{field.name.value}: %{
53+
type: #{walk_ast(field.type)},
54+
args: %{
55+
#{args |> Enum.map(&walk_ast/1) |> Enum.join(",\n")}
56+
}
57+
}
58+
""" |> String.strip
59+
end
60+
61+
def walk_ast(input = %{kind: :InputValueDefinition}) do
62+
"#{input.name.value}: %{type: #{walk_ast(input.type)}}"
63+
end
64+
65+
def walk_ast(field = %{kind: :FieldDefinition}) do
66+
"#{field.name.value}: %{type: #{walk_ast(field.type)}}"
67+
end
68+
69+
def walk_ast(type = %{kind: :NonNullType}) do
70+
"%Type.NonNull{ofType: #{walk_ast(type.type)}}"
71+
end
72+
73+
def walk_ast(type = %{kind: :ListType}) do
74+
"%Type.List{ofType: #{walk_ast(type.type)}}"
75+
end
76+
77+
def walk_ast(type = %{kind: :NamedType}) do
78+
if type.name.value in ~w(String Int ID) do
79+
"%Type.#{type.name.value}{}"
80+
else
81+
type.name.value
82+
end
83+
end
84+
85+
def walk_ast(type_def = %{kind: :EnumTypeDefinition}) do
86+
"""
87+
defmodule #{type_def.name.value} do
88+
def type do
89+
Type.Enum.new %{
90+
name: "#{type_def.name.value}",
91+
description: "#{type_def.name.value} description",
92+
values: %{
93+
#{type_def.values |> Enum.map(fn (v) -> "#{v}: %{value: 0}" end) |> Enum.join(",\n ")}
94+
}
95+
}
96+
end
97+
end
98+
"""
99+
end
100+
101+
def walk_ast(type_def = %{kind: :InterfaceTypeDefinition}) do
102+
"""
103+
defmodule #{type_def.name.value} do
104+
def type do
105+
Type.Interface.new %{
106+
name: "#{type_def.name.value}",
107+
description: "#{type_def.name.value} description",
108+
fields: %{
109+
#{type_def.fields |> Enum.map(&walk_ast/1) |> Enum.join(",\n ")}
110+
}
111+
}
112+
end
113+
end
114+
"""
115+
end
116+
117+
def walk_ast(type_def = %{kind: :UnionTypeDefinition}) do
118+
"""
119+
defmodule #{type_def.name.value} do
120+
def type do
121+
GraphQL.Type.Union.new %{
122+
name: "#{type_def.name.value}",
123+
description: "#{type_def.name.value} description",
124+
types: [#{type_def.types |> Enum.map(&walk_ast/1) |> Enum.join(", ")}]
125+
}
126+
end
127+
end
128+
"""
129+
end
130+
131+
def walk_ast(type_def = %{kind: :ScalarTypeDefinition}) do
132+
"""
133+
defmodule #{type_def.name.value} do
134+
def type do
135+
GraphQL.Type.Union.new %{
136+
name: "#{type_def.name.value}",
137+
description: "#{type_def.name.value} description",
138+
types: [#{type_def.types |> Enum.map(&walk_ast/1) |> Enum.join(", ")}]
139+
}
140+
end
141+
end
142+
"""
143+
end
144+
145+
def walk_ast(n) do
146+
"#{n.kind} not handled!!!"
147+
end
148+
149+
def interfaces(type_def) do
150+
if i = Map.get(type_def, :interfaces) do
151+
",
152+
interfaces: [#{Enum.map_join(i, ", ", &(&1.name.value))}]"
153+
else
154+
""
155+
end
156+
end
157+
end

lib/mix/tasks/compile.graphql.ex

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
defmodule Mix.Tasks.Compile.Graphql do
2+
use Mix.Task
3+
4+
@recursive true
5+
@manifest ".compile.graphql"
6+
7+
@moduledoc """
8+
Compiles `.graphql` files which have changed since the last generation.
9+
10+
Currently only handles schema files, but will support queries in future.
11+
12+
To use this you need to add the `:graphql` compiler to the front ofyour compiler chain.
13+
This just needs to be anywhere before the Elixir compiler because we are
14+
generating Elixir code.
15+
16+
In your `mix.exs` project in a Phoenix project for example:
17+
18+
compilers: [:phoenix, :graphql] ++ Mix.compilers
19+
20+
You also need to tell the GraphQL compiler which files to pick up.
21+
22+
In `config/config.exs`
23+
24+
config :graphql, source_path: "web/graphql/**/*_schema.graphql"
25+
26+
27+
## Command line options
28+
29+
* `--force` - forces compilation regardless of modification times
30+
31+
"""
32+
33+
@spec run(OptionParser.argv) :: :ok | :noop
34+
def run(args) do
35+
{opts, _, _} = OptionParser.parse(args, switches: [force: :boolean])
36+
37+
graphql_schema_glob = Application.get_env(:graphql, :source_path)
38+
39+
changed =
40+
graphql_schema_glob
41+
|> Path.wildcard
42+
|> compile_all(opts)
43+
44+
if Enum.any?(changed, &(&1 == :ok)) do
45+
:ok
46+
else
47+
:noop
48+
end
49+
end
50+
51+
@doc """
52+
Returns GraphQL manifests.
53+
"""
54+
def manifests, do: [manifest]
55+
defp manifest, do: Path.join(Mix.Project.manifest_path, @manifest)
56+
57+
def compile_all(schema_paths, opts) do
58+
Enum.map schema_paths, &(compile(&1, opts))
59+
end
60+
61+
def compile(schema_path, opts) do
62+
base_filename = extract_file_prefix(schema_path)
63+
target = base_filename <> ".ex"
64+
if opts[:force] || Mix.Utils.stale?([schema_path], [target]) do
65+
Mix.shell.info "Compiling `#{schema_path}` to `#{target}`"
66+
with target,
67+
{:ok, source_schema} <- File.read(schema_path),
68+
{:ok, generated_schema} <- GraphQL.Schema.Generator.generate(base_filename, source_schema),
69+
do: File.write!(target, generated_schema)
70+
else
71+
Mix.shell.info "Skipping `#{schema_path}`"
72+
end
73+
end
74+
75+
def extract_file_prefix(path) do
76+
Path.join(Path.dirname(path), Path.basename(path, ".graphql"))
77+
end
78+
end

0 commit comments

Comments
 (0)