Current section
Files
Jump to
Current section
Files
lib/gql_loader.ex
defmodule Wormwood.GQLLoader do
@import_regex ~r"#import \"(.*)\""
@doc """
When provided a path to a GQL document, expands all import statements and attempts to parses it with Absinthe.
Returns the query string source with imports appended.
For example:
```elixir
load_file!("assets/js/MyQuery.gql")
```
"""
@spec load_file!(binary) :: binary
def load_file!(document_path) when is_binary(document_path) do
try_load_file(document_path)
|> graphql_expand_imports(document_path)
|> try_parse_document(document_path)
end
@doc """
When provided the source code of a GQL document, expands all import statements and attempts to parses it with Absinthe.
Returns the query string source with imports appended.
For example:
```elixir
load_string!(@my_query_source)
```
"""
@spec load_string!(binary) :: binary
def load_string!(query_string) when is_binary(query_string) do
graphql_expand_imports(query_string, File.cwd!)
end
defp graphql_expand_imports(content, file_path) do
Path.expand(file_path)
|> File.dir?()
|> case do
false ->
base_dir = Path.dirname(file_path)
do_import_expansion(content, base_dir, file_path)
true ->
do_import_expansion(content, file_path, file_path)
end
end
defp do_import_expansion(content, base_dir, file_path) do
matches = Regex.scan(@import_regex, content)
graphql_inject_import_matches(content, matches, base_dir, file_path)
end
defp graphql_inject_import_matches(content, matches, dir, parent_file) do
case matches do
[] ->
content
_ ->
[_, import_path] = List.first(matches)
content_to_inject = Path.join(dir, import_path)
|> Path.expand()
|> try_import_file(parent_file)
|> graphql_expand_imports(import_path)
content <> content_to_inject
|> graphql_inject_import_matches(tl(matches), dir, parent_file)
end
end
defp try_import_file(import_path, parent_file) do
try do
try_load_file(import_path)
rescue
_e in WormwoodLoaderError ->
raise WormwoodImportError, path: import_path, parent: parent_file
end
end
defp try_load_file(path) do
File.read(path)
|> case do
{:ok, file_content} ->
file_content
{:error, reason} ->
raise WormwoodLoaderError, path: path, reason: reason
end
end
defp try_parse_document(document, src_path) do
case Absinthe.Phase.Parse.run(%Absinthe.Language.Source{body: document}) do
{:ok, _blueprint} ->
document
{:error, blueprint} ->
error = blueprint.execution.validation_errors
|> List.first()
error_location = error.locations
|> List.first()
|> Map.get(:line)
raise WormwoodParseError, path: src_path, err: error.message, line: error_location
end
end
end