# client elixir

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

Generate an Elixir client on [Req](https://hexdocs.pm/req) from an OpenAPI document.

From an AsyncAPI document, the command generates a WebSocket and Kafka client library instead; see
[apigenie-generator client elixir (AsyncAPI)](/techdocs/reference/cli-generate_client_elixir_asyncapi).

## Usage

```sh
apigenie-generator client elixir [...OPTIONS] INPUT_FILE
```

## Description

The generator writes a Mix project (Elixir 1.18 or later) depending on Req 0.5 or later and Jason.
Its code passes `mix format --check-formatted`, `mix compile --warnings-as-errors`,
`mix credo --strict` and `mix dialyzer`.

| File | Contents |
| ---- | -------- |
| `lib/<app>.ex` | the root module, with `new/1` creating a client |
| `lib/<app>/api/<tag>.ex` | a module per tag (`Default` for operations without tags), with a function per operation |
| `lib/<app>/models/*.ex` | a module per schema: structs, enums, unions and aliases, with `new/1`, `new!/1` and `to_map/1` |
| `lib/<app>/client.ex`, `api_error.ex` | the client struct and the error of operations |
| `lib/<app>/metadata.ex` | the servers and the security schemes of the document |
| `lib/<app>/runtime/*.ex`, `models/decode.ex`, `models/encode.ex` | the encoding of parameters and bodies, and the decoding of responses |
| `mix.exs`, `.formatter.exs`, `.gitignore`, `README.md` | the project |

```elixir
client =
  CarpetExpress.new(
    base_url: "https://api.example.com/v2",
    auth: [bearer_jwt: System.fetch_env!("API_TOKEN")],
    req_options: [receive_timeout: 10_000, retry: :transient]
  )

{:ok, shipment} = CarpetExpress.Api.Shipments.get_shipment(client, shipment_id: "shp_123")
shipment = CarpetExpress.Api.Shipments.get_shipment!(client, shipment_id: "shp_123")
```

### Functions

Every operation is a function named after its `operationId` in snake_case, in the module of its
first tag:

- `op(client, params \\ [], opts \\ [])`, or `op(client, body, params \\ [], opts \\ [])` when the
  operation has a request body;
- `params` is a keyword list of the path, query, header and cookie parameters, keyed by their
  names in snake_case (`shipment_id: "..."`, `if_match: ...`); missing required parameters and
  unknown ones give an error before anything is sent;
- `opts` are the options of the call: `:content_type` (the media type of the body when the
  operation accepts several), `:headers` and `:req_options` (options of `Req.request/2`, e.g.
  `retry: false` or `into: File.stream!("label.pdf")`).

A `!` variant of each function returns the body itself and raises the error.

### Responses and errors

A response with a 2XX status gives `{:ok, body}`: the body decoded into its model (`new/1` of
its module), a list of them, a string for `text/*` media types, the bytes for other media types,
or `nil` without content. Any other status gives `{:error, %ApiError{reason: :status}}`, with the
`status`, the `headers` and the decoded `body` (a problem document, the error's model). The other
reasons are `:transport` (the request failed), `:decode` (the body doesn't match its schema),
`:params` and `:auth`.

### Parameters and bodies

Parameters are encoded in their styles: `simple`, `label` and `matrix` path parameters, `form`,
`spaceDelimited`, `pipeDelimited` and `deepObject` query parameters, headers, cookies, and
parameters described with `content` as JSON. Models, enums (atoms), dates and times are encoded
with their schemas.

| Media type | Body |
| ---------- | ---- |
| JSON (`application/json`, `*+json`) | a model struct or a plain map |
| `application/x-www-form-urlencoded` | a model struct or a map, its fields in the styles of their `encoding` |
| `multipart/form-data` | a map or a keyword list of the parts: files as `{content, filename: ..., content_type: ...}` or the content alone, lists of files, values and JSON parts |
| `text/*` | a string |
| anything else | iodata, or an enumerable of chunks |

### Servers

The base URL is the first server of the document, or the `:base_url` option of `new/1`.
Operations with servers of their own are sent there, unless `operation_servers: false`.

### Security

`new/1` takes the credentials by security scheme in `:auth`, keyed by the scheme names in
snake_case (`CarpetExpress.Metadata.security_schemes/0` lists them). A credential is the value
(an API key, a token, `{user, password}` for HTTP basic) or a supplier called before each request
(`fn -> {:ok, token} end`, or `{module, function, args}`), e.g. to fetch and refresh OAuth2 tokens.
Each request uses the first security requirement of its operation whose schemes all have a
credential: API keys in headers, the query or cookies, HTTP bearer and basic, OAuth2 and OpenID
Connect tokens as bearer tokens. Mutual TLS is set up with `req_options: [connect_options:
[transport_opts: [certfile: ..., keyfile: ...]]]`.

### Types

The models are the modules of [the Elixir models](/techdocs/reference/cli-generate_client_elixir#types):
a struct per object (with `allOf` flattened), atoms for string enums, a module dispatching on the
discriminator for `oneOf` and `anyOf`, `Date`, `DateTime`, `NaiveDateTime` and `Time` for dates and
times, binaries for `format: byte` (decoded from base64) and `format: binary`,
`additional_properties` maps, and `| nil` for nullable values.

### Extensions

| Extension | Effect |
| --------- | ------ |
| `x-apigenie.codegen.name` | name of the module of a component schema, unless `module_overrides` names it |
| `x-apigenie.codegen.action-name` | name of the function of an operation without an `operationId` (with the default `action_naming`, which lists `id` first) |
| `x-apigenie.codegen.disabled` | `true` on an operation or its path item leaves the operation out |
| `x-internal` | `true` on an operation or its path item leaves the operation out, unless `internal_operations` is set |
| `x-defaultMapping` | `defaultMapping` of a discriminator in OpenAPI 3.0 and 3.1 documents: the schema of the values whose discriminator value isn't mapped |

The extensions can live in an overlay for this target instead of in the document, e.g.
`elixir.overlay.yaml`:

```yaml
overlay: 1.0.0
info:
  title: Elixir names
  version: 1.0.0
actions:
  - target: $.components.schemas.Pet
    update:
      x-apigenie:
        codegen:
          name: Animal
  - target: $.paths['/admin']
    update:
      x-internal: true
```

```sh
apigenie-generator client elixir openapi.yaml --overlay elixir.overlay.yaml -o out
```

### Limitations

- `text/event-stream` and JSON Lines responses are read whole, as a string and as bytes.
- The models don't validate `minimum`, `maximum`, `pattern` and the other constraints, and ignore
  `prefixItems`, `patternProperties` and conditional schemas.
- `readOnly` and `writeOnly` properties are fields of the same struct.
- Callbacks, webhooks and links aren't generated.

## Options

### -c, --config

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

### --overlay OVERLAY

Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the
extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those
listed under `commands.generate.client_elixir.overlay` in the configuration file (with paths
relative to it). Warnings and errors about a value an overlay set point to the overlay. Not for a
`.proto` input.

### --strict

Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated)
instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`).

### -o, --output

Directory where the project is generated.

### --package-name

Name of the Mix application, converted to snake_case (`carpet-express` gives `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 dev_deps=false`. Options given on the command line
take precedence over the configuration file. Lists are comma-separated (`-p include_tags=pets,store`)
and mappings too (`-p module_overrides=pet-owner=Owner`).

## Configuration

```yaml
version: '1.0'
commands:
  generate:
    client_elixir:
      app: carpet_express
      namespace: CarpetExpress
      exclude_tags: [internal]
```

| 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`. |
| `include_tags` | list of strings | | Generates only the operations with one of these tags. |
| `exclude_tags` | list of strings | | Skips the operations with one of these tags. |
| `skip_prune` | boolean | `false` | Keeps the component schemas that no generated operation uses. |
| `internal_operations` | boolean | `false` | Also generates the operations marked `x-internal: true`. |
| `dev_deps` | boolean | `true` | Lists credo and dialyxir as dependencies of `mix.exs` (`only: :dev`). |
| `action_naming` | list of `id`, `extension`, `url` | `id, extension, url` | Where function names come from, in order of preference. |
| `field_prefix` | string | `field_` | Prefix of struct fields whose property names give no valid name. |
| `module_overrides` | map of strings | | Module names of component schemas, e.g. `pet-owner: Owner`. |
