README.md

# elli_openapi
Library for building type-safe HTTP APIs with automatic OpenAPI documentation generation using Elli and Spectra.
This library is not ready for production use, but it wont take long to finish it.

## Usage

1. **Add to your rebar.config dependencies:**

```erlang
{deps, [
    {elli_openapi, "~> 0.1.0"}
]}.
```

2. **Start Elli with elli_openapi_handler as the callback and your routes as arguments to elli_openapi_handler:**

```erlang
%% Define your routes
Routes = [
    {<<"POST">>, <<"/api/users">>, fun user_handler:create_user/3},
    {<<"GET">>, <<"/api/users/{userId}">>, fun user_handler:get_user/3}
],

%% Configure and start Elli, preferably in you supervisor spec.
ElliOpts = [
    {callback, elli_openapi_handler},
    {callback_args, Routes},
    {port, 3000}
],

{ok, Pid} = elli:start_link(ElliOpts).
```

See the `example/` directory for a runnable example application with handler implementations.

## Handler Functions

All handler functions must follow this signature:

```erlang
handler_name(PathArgs, Headers, Body) -> {StatusCode, ResponseHeaders, ResponseBody}
```

### Arguments

1. **PathArgs** (`map()`): URL path parameters extracted from the route
   - Example: For route `<<"/api/users/{userId}">>`, PathArgs would be `#{userId => ...the provided userid...}`
   - Empty map `#{}` if no path parameters

2. **Headers** (`map()`): HTTP request headers with atom keys
   - Example: `#{'Authorization' => ..., 'Content-Type' => ...}`
   - Required headers must be declared in the function spec

3. **Body** (`any()`): Request body, automatically decoded based on the type in your function spec
   - Plain text requests: `binary()`
   - JSON requests: `map()` or record type
   - The library validates and decodes the body according to your spec

### Return Value

Must be a 3-tuple: `{StatusCode, ResponseHeaders, ResponseBody}`

- **StatusCode**: HTTP status code integer (200, 201, 400, etc.)
- **ResponseHeaders**: Map with atom keys (e.g., `#{'Location' => <<"...">>, 'ETag' => <<"...">>}`)
- **ResponseBody**: Response body (record, map, or binary) - will be encoded based on content type

To return different status codes from the same handler, use union types in your function spec where each branch represents a possible response:

```erlang
-spec my_handler(PathArgs, Headers, Body) ->
    {200, Headers1, SuccessBody}
    | {400, Headers2, ErrorBody}
    | {404, Headers3, NotFoundBody}.
```

For complete handler examples, see `example/src/elli_openapi_demo.erl`.

## Example Application

The `example/` directory contains a runnable demo application showcasing multiple handler implementations including user management, echo, status updates, and item updates with conflict detection.

To run the example:

```bash
cd example
rebar3 shell
```

The demo starts on port 3000. Access the API documentation at:
- Swagger UI: http://localhost:3000/swagger
- ReDoc: http://localhost:3000/redoc
- OpenAPI JSON: http://localhost:3000/api-docs