parse.ex (3855B)
1 defmodule Absinthe.Phase.Parse do 2 @moduledoc false 3 4 use Absinthe.Phase 5 6 alias Absinthe.{Blueprint, Language, Phase} 7 8 # This is because Dialyzer is telling us tokenizing can never fail, 9 # but we know it's possible. 10 @dialyzer {:no_match, run: 2} 11 @spec run(Language.Source.t() | %Blueprint{}, Keyword.t()) :: Phase.result_t() 12 def run(input, options \\ []) 13 14 def run(%Absinthe.Blueprint{} = blueprint, options) do 15 options = Map.new(options) 16 17 case parse(blueprint.input) do 18 {:ok, value} -> 19 {:ok, %{blueprint | input: value}} 20 21 {:error, error} -> 22 blueprint 23 |> add_validation_error(error) 24 |> handle_error(options) 25 end 26 end 27 28 def run(input, options) do 29 run(%Absinthe.Blueprint{input: input}, options) 30 end 31 32 # This is because Dialyzer is telling us tokenizing can never fail, 33 # but we know it's possible. 34 @dialyzer {:no_unused, add_validation_error: 2} 35 defp add_validation_error(bp, error) do 36 put_in(bp.execution.validation_errors, [error]) 37 end 38 39 def handle_error(blueprint, %{jump_phases: true, result_phase: abort_phase}) do 40 {:jump, blueprint, abort_phase} 41 end 42 43 def handle_error(blueprint, _) do 44 {:error, blueprint} 45 end 46 47 @spec tokenize(binary) :: {:ok, [tuple]} | {:error, String.t()} 48 def tokenize(input) do 49 case Absinthe.Lexer.tokenize(input) do 50 {:error, rest, loc} -> 51 {:error, format_raw_parse_error({:lexer, rest, loc})} 52 53 other -> 54 other 55 end 56 end 57 58 # This is because Dialyzer is telling us tokenizing can never fail, 59 # but we know it's possible. 60 @dialyzer {:no_match, parse: 1} 61 @spec parse(binary | Language.Source.t()) :: {:ok, Language.Document.t()} | {:error, tuple} 62 defp parse(input) when is_binary(input) do 63 parse(%Language.Source{body: input}) 64 end 65 66 defp parse(input) do 67 try do 68 case tokenize(input.body) do 69 {:ok, []} -> 70 {:ok, %Language.Document{}} 71 72 {:ok, tokens} -> 73 case :absinthe_parser.parse(tokens) do 74 {:ok, _doc} = result -> 75 result 76 77 {:error, raw_error} -> 78 {:error, format_raw_parse_error(raw_error)} 79 end 80 81 other -> 82 other 83 end 84 rescue 85 error -> 86 {:error, format_raw_parse_error(error)} 87 end 88 end 89 90 @spec format_raw_parse_error({{integer, integer}, :absinthe_parser, [charlist]}) :: 91 Phase.Error.t() 92 defp format_raw_parse_error({{line, column}, :absinthe_parser, msgs}) do 93 message = msgs |> Enum.map(&to_string/1) |> Enum.join("") 94 %Phase.Error{message: message, locations: [%{line: line, column: column}], phase: __MODULE__} 95 end 96 97 @spec format_raw_parse_error({integer, :absinthe_parser, [charlist]}) :: 98 Phase.Error.t() 99 defp format_raw_parse_error({line, :absinthe_parser, msgs}) do 100 message = msgs |> Enum.map(&to_string/1) |> Enum.join("") 101 %Phase.Error{message: message, locations: [%{line: line, column: 0}], phase: __MODULE__} 102 end 103 104 @spec format_raw_parse_error({:lexer, String.t(), {line :: pos_integer, column :: pos_integer}}) :: 105 Phase.Error.t() 106 defp format_raw_parse_error({:lexer, rest, {line, column}}) do 107 sample_slice = String.slice(rest, 0, 10) 108 sample = if String.valid?(sample_slice), do: sample_slice, else: inspect(sample_slice) 109 110 message = "Parsing failed at `#{sample}`" 111 %Phase.Error{message: message, locations: [%{line: line, column: column}], phase: __MODULE__} 112 end 113 114 @unknown_error_msg "An unknown error occurred during parsing" 115 @spec format_raw_parse_error(map) :: Phase.Error.t() 116 defp format_raw_parse_error(%{} = error) do 117 detail = 118 if Exception.exception?(error) do 119 ": " <> Exception.message(error) 120 else 121 "" 122 end 123 124 %Phase.Error{message: @unknown_error_msg <> detail, phase: __MODULE__} 125 end 126 end