# server absinthe

Source: https://apigenie.pl/techdocs/reference/cli-generate_server_absinthe

Generate an [Absinthe](https://hexdocs.pm/absinthe) server from a GraphQL schema (SDL).

## Usage

```sh
apigenie-generator server absinthe [...OPTIONS] INPUT_FILE
```

The input is a GraphQL schema: a `.graphql`, `.graphqls` or `.gql` file. It must be valid and
self-contained, and have a query type.

## Description

The generator writes a Mix project (Elixir 1.18 or later) that depends on Absinthe 1.12,
Absinthe.Plug, Plug, Bandit and Jason. The code passes `mix format --check-formatted`,
`mix compile --warnings-as-errors` and `mix credo --strict`, and `mix run --no-halt` serves it.

| File | Contents |
| ---- | -------- |
| `priv/graphql/schema.graphql` | the schema, as written |
| `lib/<app>/schema.ex` | the schema module: `import_sdl` of the schema, and `hydrate/2` attaching resolvers, type resolution and scalars |
| `lib/<app>/schema/directives.ex` | the custom directives of the schema, declared in a prototype schema |
| `lib/<app>/resolvers/<type>.ex` | a behaviour per object type with root fields or fields with arguments, with a callback per field |
| `lib/<app>/resolvers.ex` | the lookup of the configured implementations |
| `lib/<app>/type_resolver.ex` | the object types of the values of interfaces and unions |
| `lib/<app>/scalars.ex` | the parsing and serialization of the custom scalars |
| `lib/<app>/loader.ex` | the Dataloader source, with the `dataloader` option |
| `lib/<app>/router.ex` | the Plug router: the GraphQL endpoint, and GraphiQL in the dev environment |
| `lib/<app>/application.ex` | the application, starting Bandit |

### The schema

`<Root>.Schema` imports the schema from `priv/graphql/schema.graphql` with `import_sdl`, and
recompiles when the file changes. Absinthe keeps everything the schema says:
- descriptions of types, fields, arguments, enum values and input fields;
- deprecations of fields, arguments, input fields and enum values;
- default values, interfaces implementing interfaces, and `@oneOf` input types, which it enforces;
- type extensions.

Two things need code next to the SDL:
- **Custom directives** need a prototype schema. Absinthe applies only the directives its
  prototype schema declares, so `<Root>.Schema.Directives` declares the directives of the
  schema, along with the enums their arguments use.
- **Custom scalars** need parse and serialize functions, which `hydrate/2` attaches.

### Resolvers and implementations

Root fields, and fields with arguments, are resolved by the module of their type:
`<Root>.Resolvers.Query`, `<Root>.Resolvers.Mutation`, `<Root>.Resolvers.Warehouse`... Each one is
a behaviour with a callback per field:
- a root field's callback receives the arguments (a map with atom keys, as Absinthe gives them)
  and the resolution;
- a nested field's callback receives the parent value first.

```elixir
@callback track_shipment(args :: track_shipment_args(), resolution :: Absinthe.Resolution.t()) ::
            Resolvers.result()
```

Implement the behaviours and configure the implementations in `config/config.exs`. Regenerating
never overwrites them:

```elixir
config :carpet_express, CarpetExpress.Resolvers.Query, impl: MyApp.Query
```

A callback returns `{:ok, value}` or `{:error, reason}`. Fields without an implementation answer
an error ("Query.trackShipment is not implemented"). The other fields read their value from the
parent value with Absinthe's default resolution: the key that is the snake_case name of the field,
in a map or a struct.

### Interfaces and unions

`<Root>.TypeResolver` resolves the object type of a value of an interface or a union:
- a map with a `__typename` key resolves to the type it names;
- other values go to the `resolve_type(type, value, resolution)` callback of the implementation
  configured for `<Root>.TypeResolver`.

### Scalars

`<Root>.Scalars` has a `parse_<scalar>/1` and a `serialize_<scalar>/1` function per custom scalar.
Until an implementation of its `parse/2` and `serialize/2` callbacks is configured, they pass
string, number and boolean literals and values through as they are.

### Router and GraphiQL

`<Root>.Router` parses JSON, form and multipart bodies (`Absinthe.Plug.Parser`) and forwards the
endpoint's path (`/graphql`) to `Absinthe.Plug`, which takes queries and mutations by POST and GET.
When the application's `:graphiql` setting is true, as `config/dev.exs` sets it, the router also
serves GraphiQL at `/graphiql`. Bandit listens on `127.0.0.1:4000` in development and on all
interfaces in production; the `PORT` environment variable sets the port.

### Dataloader

With `dataloader: true`, `context/1` puts a Dataloader in the context of the resolvers
(`resolution.context.loader`), with a `Dataloader.KV` source named `<Root>.Loader`. Its batches go
to the `fetch(batch_key, ids)` callback of the implementation configured for `<Root>.Loader`.

### Limitations

- Subscriptions get their resolvers, but the application has no subscription transport
  (Absinthe.Phoenix channels), so they aren't served.
- Absinthe's introspection doesn't expose the `specifiedByURL` of scalars, although the schema
  keeps `@specifiedBy`.
- Absinthe can't import extensions of a root type that the schema definition gives another name
  (`schema { query: Root }` with `extend type Root`). The generator reports this as an error.
- The arguments of custom directives can only have built-in scalar and enum types.

## Options

### -c, --config

Path to the configuration file to use. See the "Configuration" section for more information.

### --no-config

Doesn't look for `apigenie.yaml` (or `.apigenie.yaml`) in the current directory and its parents.

### -o, --output

Directory where the project is generated.

### --package-name

Name of the Mix application, converted to snake_case. `carpet-express` gives the application
`carpet_express` and the root module `CarpetExpress`. Defaults to the name of the output directory.

### --package-version

Version of the Mix project; `0.1.0` by default.

### -p, --property NAME=VALUE

Sets a generator option (see below), e.g. `-p dataloader=true`. Options given on the command line
take precedence over the configuration file.

## Configuration

```yaml
version: '1.0'
commands:
  generate:
    server_absinthe:
      app: carpet_express
      path: /api/graphql
      dataloader: true
```

| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `app` | string | name of the output directory | Name of the Mix application (snake_case, the directory under `lib/`); also set by `--package-name`. |
| `namespace` | string | the application in PascalCase | Root module of the generated modules, e.g. `CarpetExpress`. |
| `path` | string | `/graphql` | Path of the GraphQL endpoint. |
| `graphiql_path` | string | `/graphiql` | Path of GraphiQL, served in the dev environment. |
| `dataloader` | boolean | `false` | Adds a Dataloader to the context of the resolvers. |
| `dev_deps` | boolean | `true` | Lists credo as a dependency of `mix.exs` (`only: [:dev, :test]`). |
