Packages
absinthe
0.2.2
1.11.0
1.10.2
1.10.1
1.10.0
1.9.1
1.9.0
1.8.0
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.8
1.6.7
retired
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.6.0-rc.1
1.6.0-rc.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.5.0-rc.5
1.5.0-rc.4
1.5.0-rc.3
1.5.0-rc.2
1.5.0-rc.1
1.5.0-rc.0
1.5.0-beta.2
1.5.0-beta.1
1.5.0-beta.0
1.5.0-alpha.4
1.5.0-alpha.3
1.5.0-alpha.2
1.5.0-alpha.1
1.5.0-alpha.0
1.4.16
1.4.15
1.4.14
1.4.13
1.4.12
1.4.11
1.4.10
1.4.9
1.4.8
retired
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.4.0-rc.3
1.4.0-rc.2
1.4.0-rc.1
1.4.0-rc.0
1.4.0-beta.5
1.4.0-beta.4
1.4.0-beta.3
1.4.0-beta.2
1.4.0-beta.1
1.3.2
1.3.1
1.3.0
1.3.0-rc.0
1.3.0-beta.2
1.3.0-beta.1
1.3.0-beta.0
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc.0
1.2.0-beta.0
1.2.0-alpha0
1.2.0-alpha.2
1.2.0-alpha.1
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.0
0.5.2
0.5.1
0.5.0
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.2.3
0.2.2
0.2.1
0.1.0
GraphQL for Elixir
Current section
Files
Jump to
Current section
Files
lib/absinthe.ex
defmodule Absinthe do
@moduledoc """
Documentation for the Absinthe package, a toolkit for building GraphQL
APIs with Elixir.
Absinthe aims to handle authoring GraphQL API schemas -- then supporting
their introspection, validation, and execution according to the
[GraphQL specification](https://facebook.github.io/graphql/).
## Building HTTP APIs
**IMPORTANT**: For HTTP, you'll probably want to use
[AbsinthePlug](https://hex.pm/projects/absinthe_plug) instead of executing
GraphQL query documents yourself. Absinthe doesn't know or care about HTTP,
so keep that in mind while reading through the documentation. While you'll
be building schemas just as in the examples here, the actual calls to
`Absinthe.run/3` and its friends are best left to
[AbsinthePlug](https://hex.pm/projects/absinthe_plug) if you're providing an
HTTP API.
## Ecosystem
Here are some additional projects you're likely to use in conjunction with
Absinthe to launch an API:
* [Ecto](http://hexdocs.pm/ecto) - a language integrated query and
database wrapper.
* [Phoenix](http://hexdocs.pm/phoenix) - the Phoenix web framework.
* [Plug](http://hexdocs.pm/plug) - a specification and conveniences
for composable modules in between web applications.
* [Poison](http://hexdocs.pm/poison) - JSON serialization.
## GraphQL Basics
For a grounding in GraphQL, I recommend you read through the following articles:
* The [GraphQL Introduction](https://facebook.github.io/react/blog/2015/05/01/graphql-introduction.html) and [GraphQL: A data query language](https://code.facebook.com/posts/1691455094417024/graphql-a-data-query-language/) posts from Facebook.
* The [Your First GraphQL Server](https://medium.com/@clayallsopp/your-first-graphql-server-3c766ab4f0a2#.m78ybemas) Medium post by Clay Allsopp. (Note this uses the [JavaScript GraphQL reference implementation](https://github.com/graphql/graphql-js).)
* Other blog posts that pop up. GraphQL is young!
* For the ambitious, the draft [GraphQL Specification](https://facebook.github.io/graphql/).
You may also be interested in how GraphQL is used by [Relay](https://facebook.github.io/relay/), a "JavaScript frameword for building data-driven React applications."
## GraphQL using Absinthe
The first thing you need to do is define a schema, we do this
by using `Absinthe.Schema`.
Here we'll build a basic schema that defines one query field; a
way to retrieve the data for an `item`, given an `id`. Users of
the API can then decide what fields of the `item` they'd like
returned.
```
defmodule App.Schema do
use Absinthe.Schema
@fake_db %{
"foo" => %{id: "foo", name: "Foo", value: 4},
"bar" => %{id: "bar", name: "Bar", value: 5}
}
def query do
%Absinthe.Type.Object{
fields: fields(
item: [
type: :item,
description: "Get an item by ID",
args: args(
id: [type: :id, description: "The ID of the item"]
),
resolve: fn %{id: id}, _ ->
{:ok, Map.get(@fake_db, id)}
end
]
)
}
end
@absinthe :type
def item do
%Absinthe.Type.Object{
description: "A valuable item",
fields: fields(
id: [type: :id],
name: [type: :string, description: "The item's name"],
value: [type: :integer, description: "Recently appraised value"]
)
}
end
end
```
Now we'll execute a query document against it with
`run/2` or `run/3` (which return tuples), or their exception-raising
equivalents, `run!/2` and `run!/3.
Let's get the `name` of an `item` with `id` `"foo"`:
```
\"""
{
item(id: "foo") {
name
}
}
\"""
|> Absinthe.run(App.Schema)
```
Results are returned in a tuple, and are maps with `:data` and/or `:errors` keys, suitable for serialization
back to the client.
```
{:ok, %{data: %{"name" => "Foo"}}}
```
You can also provide values for variables defined in the query document
(supporting, eg, values passed as query string parameters):
```
\"""
query GetItemById($id: ID) {
item(id: $id) {
name
}
}
\"""
|> Absinthe.run(App.Schema, variables: %{id: params[:item_id]})
```
The result, if `params[:item_id]` was `"foo"`, would be the same:
```
{:ok, %{data: %{"name" => "Foo"}}}
```
`run!/2` and `run!/3` operate similarly, except they will raise
`Absinthe.SytaxError` and `Absinthe.ExecutionError` if they cannot
parse/execute the document.
"""
defmodule ExecutionError do
@moduledoc """
An error during execution.
"""
defexception message: "execution failed"
end
defmodule SyntaxError do
@moduledoc """
An error during parsing.
"""
defexception location: nil, msg: ""
def message(exception) do
"#{exception.msg} on line #{exception.location.line}"
end
end
@doc false
@spec tokenize(binary) :: {:ok, [tuple]} | {:error, binary}
def tokenize(input) do
case :absinthe_lexer.string(input |> to_char_list) do
{:ok, tokens, _line_count} ->
{:ok, tokens}
{:error, raw_error, _} ->
{:error, format_raw_parse_error(raw_error)}
end
end
@doc false
@spec parse(binary) :: {:ok, Absinthe.Language.Document.t} | {:error, tuple}
@spec parse(Absinthe.Language.Source.t) :: {:ok, Absinthe.Language.Document.t} | {:error, tuple}
def parse(input) when is_binary(input) do
parse(%Absinthe.Language.Source{body: input})
end
def parse(input) do
case input.body |> tokenize do
{:ok, []} -> {:ok, %Absinthe.Language.Document{}}
{:ok, tokens} ->
case :absinthe_parser.parse(tokens) do
{:ok, _} = result ->
result
{:error, raw_error} ->
{:error, format_raw_parse_error(raw_error)}
end
other -> other
end
end
@doc false
@spec parse!(binary) :: Absinthe.Language.Document.t
@spec parse!(Absinthe.Language.Source.t) :: Absinthe.Language.Document.t
def parse!(input) when is_binary(input) do
parse!(%Absinthe.Language.Source{body: input})
end
def parse!(input) do
case parse(input) do
{:ok, result} -> result
{:error, err} -> raise SyntaxError, source: input, msg: err.message, location: err.locations[0]
end
end
@doc """
Evaluates a query document against a schema, with options.
## Options
* `:adapter` - The name of the adapter to use. See the `Absinthe.Adapter`
behaviour and the `Absinthe.Adapter.Passthrough` and
`Absinthe.Adapter.LanguageConventions` modules that implement it.
(`Absinthe.Adapter.Passthrough` is the default value for this option.)
* `:operation_name` - If more than one operation is present in the provided
query document, this must be provided to select which operation to execute.
* `:variables` - A map of provided variable values to be used when filling in
arguments in the provided query document.
## Examples
```
\"""
query GetItemById($id: ID) {
item(id: $id) {
name
}
}
\"""
|> Absinthe.run(App.Schema, variables: %{id: params[:item_id]})
```
See the `Absinthe` module documentation for more examples.
"""
@spec run(binary | Absinthe.Language.Source.t | Absinthe.Language.Document.t, atom | Absinthe.Schema.t, Keyword.t) :: {:ok, Absinthe.Execution.result_t} | {:error, any}
def run(%Absinthe.Language.Document{} = document, schema, options) do
case execute(schema, document, options) do
{:ok, result} ->
{:ok, result}
other ->
other
end
end
def run(input, schema, options) do
case parse(input) do
{:ok, document} ->
run(document, schema, options)
{:error, err} ->
{:ok, %{errors: [err]}}
other ->
other
end
end
@doc """
Evaluates a query document against a schema, without options.
## Examples
```
\"""
{
item(id: "foo") {
name
}
}
\"""
|> Absinthe.run(App.Schema)
```
Also see `Absinthe.run/3`
"""
@spec run(binary | Absinthe.Language.Source.t | Absinthe.Language.Document.t, atom | Absinthe.Schema.t) :: {:ok, Absinthe.Execution.result_t} | {:error, any}
def run(input, schema), do: run(input, schema, [])
# TODO: Support modification by adapter
# Convert a raw parser error into an `Execution.error_t`
@doc false
@spec format_raw_parse_error({integer, :absinthe_parser, [char_list]}) :: Execution.error_t
defp format_raw_parse_error({line, :absinthe_parser, msgs}) do
message = msgs |> Enum.map(&to_string/1) |> Enum.join("")
%{message: message, locations: [%{line: line, column: 0}]}
end
@spec format_raw_parse_error({integer, :absinthe_lexer, {atom, char_list}}) :: Execution.error_t
defp format_raw_parse_error({line, :absinthe_lexer, {problem, field}}) do
message = "#{problem}: #{field}"
%{message: message, locations: [%{line: line, column: 0}]}
end
@doc """
Evaluates a query document against a schema, without options.
## Options
See `run/3` for the available options.
"""
@spec run!(binary | Absinthe.Language.Source.t | Absinthe.Language.Document.t, atom | Absinthe.Schema.t, Keyword.t) :: Absinthe.Execution.result_t
def run!(input, schema, options) do
case run(input, schema, options) do
{:ok, result} -> result
{:error, err} -> raise ExecutionError, message: err
end
end
@doc """
Evaluates a query document against a schema, with options, raising an
`Absinthe.SyntaxErorr` or `Absinthe.ExecutionError` if a problem occurs.
"""
@spec run!(binary | Absinthe.Language.Source.t | Absinthe.Language.Document.t, atom | Absinthe.Schema.t) :: Absinthe.Execution.result_t
def run!(input, schema), do: run!(input, schema, [])
@spec find_schema(Absinthe.Schema.t | atom) :: Absinthe.Schema.t
defp find_schema(schema_module) when is_atom(schema_module), do: schema_module.schema
defp find_schema(schema), do: schema
#
# EXECUTION
#
@spec execute(Absinthe.Schema.t | atom, Absinthe.Language.Document.t, Keyword.t) :: Absinthe.Execution.result_t
defp execute(schema_ref, document, options) do
schema = find_schema(schema_ref)
%Absinthe.Execution{schema: schema, document: document}
|> Absinthe.Execution.run(options)
end
# TODO: Do separate validation phase here
@doc """
Validate a document.
Note this is currently a stub that merely returns `:ok`,
as validation and execution are currently conflated.
Track https://github.com/CargoSense/absinthe/issues/17 for
progress.
"""
@spec validate(Language.Document.t, atom | Schema.t) :: :ok
def validate(_doc, _schema) do
:ok
end
end