# JSON API and OpenAPI

Iwf JSON routes use the same typed route metadata as HTML routes. The
goal is one source of truth for request decoding, response typing, links,
redirects, form compatibility, and OpenAPI.

OpenAPI generation lives in the separate `openapi` package. Add `openapi` to the
application package `depends` and import `Iwf.OpenApi` where you build docs
routes or print the generated document.

## 1. JSON Routes

Use one controller handler shape for HTML and JSON. Path and query inputs come
from the route string; the body comes from the second handler argument; response
metadata comes from the handler result type.

```idris
record CommentParams where
  constructor MkCommentParams
  slug : String
  notify : Maybe Bool

record CommentBody where
  constructor MkCommentBody
  body : String

record CommentEnvelope where
  constructor MkCommentEnvelope
  commentId : Integer
  body : String

%runElab deriveDto "CommentBody"
%runElab deriveDto "CommentEnvelope"

createCommentApi : CommentParams -> CommentBody -> ControllerContext -> CommentEnvelope
createCommentApi params body context =
  createComment params.slug params.notify body.body

createCommentRoute : RouteSpec CommentParams CommentBody CommentEnvelope
createCommentRoute =
  post "/api/articles/{slug}/comments?{notify}"
    |> params CommentParams
    |> body CommentBody
    |> respondsJson CommentEnvelope
    |> describe "Create comment"

createCommentBinding : BoundRoute
createCommentBinding =
  handledBy createCommentRoute createCommentApi
```

`deriveDto` is the canonical helper for records that are shared between JSON
decoding, JSON encoding, form decoding, and OpenAPI schema generation. It derives
`Show`, `Eq`, `ToJSON`, `FromJSON`, `ToSchema`, and `FormFields`, which also
creates the generated form field values used by typed forms and request-body
decoding.

The framework decodes path params, query params, and the request body before the
handler runs. JSON and urlencoded form bodies share the same handler shape; the
server picks the decoder from `Content-Type`. The route spec records the JSON
response type, and `handledBy` checks that the handler returns that type.

## 2. Content Negotiation

Use `respondNegotiated` inside a normal context handler when a route supports
both HTML and JSON:

```idris
record ArticleInput where
  constructor MkArticleInput
  slug : String

articleShow : ArticleInput -> ControllerContext -> Response
articleShow input context =
  respondNegotiated
    context.request
    (articleHtml input.slug)
    (articlePayload input.slug)

articleRoute : RouteSpec ArticleInput NoInput Response
articleRoute =
  get "/article/{slug}" |> params ArticleInput |> html

articleBinding : BoundRoute
articleBinding =
  handledBy articleRoute articleShow
```

The route returns HTML by default and JSON when the request `Accept` header
prefers `application/json`. HTMX headers do not force a JSON request into an
HTML fragment.

Use `respondsJson` when a route returns JSON data, or leave the response type as
`Response` when the handler owns content negotiation directly.

## 3. OpenAPI Info

Describe the API:

```idris
import Iwf.OpenApi

apiInfo : OpenApiInfo
apiInfo =
  MkOpenApiInfo
    "Conduit API"
    "1.0.0"
    (Just "RealWorld-style Iwf API")
```

Mount Swagger UI:

```idris
routesWithDocs : Routes
routesWithDocs =
  appRoutes ++ swaggerUiRoutes "/docs" apiInfo appRoutes
```

Add generation options when the document needs deployment metadata, security
metadata, tags, external docs, or schema overrides:

```idris
apiOptions : OpenApiOptions
apiOptions =
  MkOpenApiOptions
    [MkOpenApiServer "https://api.example.com" (Just "Production")]
    [MkOpenApiSecurityScheme "bearerAuth" bearerSecurityScheme]
    [MkOpenApiSecurityRequirement "bearerAuth" []]
    [MkOpenApiTag "Articles" (Just "Article endpoints")]
    (Just (MkOpenApiExternalDocs (Just "API guide") "https://docs.example.com/api"))
    []

docs : OpenJson
docs =
  openApiDocumentWithOptions apiInfo apiOptions appRoutes

routesWithOptions : Routes
routesWithOptions =
  appRoutes ++ swaggerUiRoutesWithOptions "/docs" apiInfo apiOptions appRoutes
```

Open:

```text
http://127.0.0.1:8082/docs
http://127.0.0.1:8082/docs/openapi.json
```

The canonical app exposes its generated document at:

```text
http://127.0.0.1:8082/docs/openapi.json
```

## 4. Swagger UI Customization

`swaggerUiRoutes` is the standard mount for local API docs. It creates two
undocumented routes: the HTML shell at the path you pass in and the generated
document at `<path>/openapi.json`.

Use `swaggerUiHtml` when the app wants to own the route shape but keep Iwf's
default Swagger UI shell:

```idris
customDocsRoutes : Routes
customDocsRoutes =
  [ undocumentedRoute [GET, HEAD] "/internal/api-docs"
      (\_ => okHtml (swaggerUiHtml apiInfo "/internal/openapi.json"))
  , undocumentedRoute [GET, HEAD] "/internal/openapi.json"
      (\_ => okJson (show (openApiDocumentWithOptions apiInfo apiOptions appRoutes)))
  ]
```

For deeper customization, mount an app-owned HTML route and point Swagger UI at
`show (openApiDocumentWithOptions apiInfo apiOptions appRoutes)`. The built-in
shell uses the Swagger UI CDN and a fixed `SwaggerUIBundle` setup; apps that
need different assets or initialization code should own the HTML route and reuse
the generated JSON document.

## 5. OpenAPI Options

`OpenApiOptions` covers document-level metadata:

- `servers`
- `securitySchemes`
- `security`
- `tags`
- `externalDocs`
- `componentSchemaOverrides`

Use `componentSchemaOverrides` when a generated schema needs a hand-authored
OpenAPI shape. Overrides are keyed by component schema name and replace the
generated schema for that name. Extra override names are added to
`components.schemas`.

## 6. Response Schema Checks

Use `validateJsonTextAgainstSchema` in tests for JSON endpoints that also publish
OpenAPI schemas:

```idris
response : Response
response =
  runRoutes appRoutes (testGet "/api/articles/1")

schemaCheck : Either String ()
schemaCheck =
  validateJsonTextAgainstSchema articlePayload response.body
```

The validator checks required fields, optional `null`, arrays, nested objects,
scalar JSON kinds, and enum values against `SchemaDoc`.

## 7. What Is Documented

Documented route metadata includes:

- path params
- query params
- request body schemas
- response body schemas
- supported methods
- summaries
- scalar formats for dates, datetimes, UUIDs, IDs, enums, arrays, and optional
  values

Only documented routes appear in OpenAPI. Use `undocumentedRoute` for internal
routes such as health checks, framework assets, and private operational
endpoints.

## 8. RealWorld Shape

The canonical app exposes browser pages and API routes for:

- login and registration
- current user
- profiles
- follow/unfollow
- article CRUD
- comments
- favorites
- feeds
- tags

Those routes share types and validation with the HTML side where practical.

## Next

Read [Sessions, Cookies, Flash, and CSRF](sessions-cookies-flash-csrf.md).
