# Output and status

`output:` names what the route answers with: an `Output` type, an object
made to be sent to clients. It must be exactly what the action returns, so
the route shows its contract without anyone opening the action.

A route that succeeds answers 200, unless `status:` says otherwise:

```jadpo
POST / {
    input: CreateTodo
    output: TodoView
    status: SuccessStatus.created
    run: create_todo(input, current_principal.user)
}
```

| `status:` | HTTP | Needs |
|---|---|---|
| left out (`SuccessStatus.ok`) | 200 | `output:` |
| `SuccessStatus.created` | 201 | `output:` |
| `SuccessStatus.accepted` | 202 | `output:`; the work carries on in the background ([Background work](/docs/jobs-and-events.md)) |
| `SuccessStatus.no_content` | 204 | no `output:`; the action returns `Unit` |

Only the output's declared fields are sent. `IssuedToken` and `IssuedKey`,
the built-in types for an issued sign-in token and a service's key
([Sign-in and identity](/docs/identity.md)), are sent as the objects they are: an `id`,
`started_at` and `ends_at`.

A route never writes a failure's status. It comes from the kind of each
failure the action can produce ([Failures](/docs/failures.md)).

## Map layers

A route may also answer with a map layer: a `FeatureCollection<G, P>`, a
collection of shapes of geometry `G`, each with properties of type `P`
([Geospatial](/docs/geospatial.md)). It is sent bare, as the whole response,
in plain RFC 7946 GeoJSON, so a mapping library fetches it as its source.
A `GET` gets one from a query ([Reads and writes](/docs/reads-and-writes.md),
"A query giving a map layer"); a route with another method gets one from
an action that builds it.

- **Only a collection, only bare.** A `List` or `Page` of layers, or a lone
  `Feature<G, P>`, is `route.output_not_output`. A mapping library takes a
  collection as its source, and nothing asks for the others.
- **What `P` may not hold** is what an `Output`'s fields may not: a caller,
  a password's hash or a secret value, however deep. `P` is an `Object`,
  which could hold any of them, so the route checks it: a layer whose
  properties hold one is `route.output_not_output` at `output:`, naming
  what and where. The same search looks inside a feature's properties
  wherever a layer appears, so an `Output` with a layer field whose
  properties hold a password's hash is refused at that field.
- **Field policy.** A layer an action returns is shown to its caller, just
  as an `Output` the action builds is. So every `return` of a
  `FeatureCollection` in an action carries only fields every caller of the
  action may read. Otherwise it is `policy.output_field_unproved` at the
  value returned ([Field restrictions](/docs/field-restrictions.md)),
  whichever route or action calls it.
- **Sent as GeoJSON.** The response's body is the collection as
  `json.write` writes it, `{"type":"FeatureCollection","features":[...]}`.
  A route test reads it back through the same codec, so `response.output`
  is the layer the action returned.

## Sending raw bytes

A route's body doesn't have to be JSON. `sends:` names the media type of a
body of raw bytes, on a route whose `Output` has exactly one field of a type
built on `Bytes`. That field is the body, with nothing around it. Here a
land registry keeps a scan of each deed, and a client opens the PDF it is
sent:

```jadpo
/// A scan of a deed, as a PDF.
type DeedScan = Output {
    pdf: Bytes
}
```

```jadpo
use titles/entities { Deed }
use titles/types { DeedRef, DeedScan }
use titles/failures { DeedNotFound }

/// A deed's scan.
query scan_of(deed_ref: DeedRef) fails DeedNotFound -> DeedScan {
    from: Deed
    where: Deed.deed_ref == deed_ref
    missing: DeedNotFound
}
```

```jadpo
use titles/types { DeedRef, DeedScan }
use titles/queries { scan_of }

/// Scans of deeds, by reference.
routes Deeds {
    prefix: /deeds

    /// A deed's scan, which never changes once it is made.
    GET /{deed_ref: DeedRef} {
        authentication: RouteAuthentication.none
        output: DeedScan
        sends: "application/pdf"
        cache: immutable
        run: scan_of(deed_ref)
    }
}
```

In a route test, `response.output` is the `Output`, its field the bytes sent:

```jadpo
use titles/entities { Deed }
use titles/types { DeedScan }
use titles/actions { add_deed }

test "a deed's scan is sent as it was kept" {
    var deed: Deed = attempt add_deed()
    var response: Response = request {
        method: HttpMethod.get
        path: "/deeds/{deed_ref}"
        params: {
            deed_ref: deed.deed_ref
        }
    }
    assert response.status == 200
    assert response.output == DeedScan {
        pdf: hash.sha256("the scan")
    }
}

test "a reference no deed has is not found" {
    var response: Response = request {
        method: HttpMethod.get
        path: "/deeds/{deed_ref}"
        params: {
            deed_ref: "D999999"
        }
    }
    assert response.status == 404
    assert response.code == "deed_not_found"
}
```

- **One field, always there.** An `Output` of any other shape is
  `route.sends_invalid`, and so is a `Bytes?` field, a `List` or `Page`, a map
  layer or an `AiStream`. A body has no room for anything else, and nothing
  to send for `none`.
- **A media type it can write in a header.** A lowercase `type/subtype`, with
  any parameters after a `;`, as in `text/csv; charset=utf-8`. JSON isn't one:
  JSON is what an `Output`'s own fields are sent as, so a route can't send a
  JSON body nothing declares. Nor is `text/event-stream`, which is what an
  `AiStream` is sent as.
- **What runs it is unchanged.** `output:` is still exactly what the route runs
  returns. A `GET` runs a query or an action the compiler proves only reads,
  and any other method an action.
- **The response.** The route's status, `Content-Type` as written,
  `Content-Length` and `X-Content-Type-Options: nosniff`, so a browser takes
  the body as what it says it is. A `HEAD` gets the same head, its length
  included, and no body.
- **Failures stay JSON.** Everything but the success answers as any route's
  does, in the failure envelope, with `Content-Type: application/json`.
- **Not with `settle:`.** That is `route.sends_invalid`: while the work carries
  on, `settle:` answers with how far it has got, which raw bytes can't say.
- **A repeat gets the same bytes.** Under an `Idempotency-Key`, a route of
  another method keeps what it sent as any route does, to the byte.
- **The field is read as any field is.** Policy and `personal:` marks apply
  whichever way it is sent: a field not every caller may read is
  `policy.output_field_unproved`, and a read of a marked field counts as one
  ([Field restrictions](/docs/field-restrictions.md),
  [Marking personal fields](/docs/marking-personal-fields.md)).
  Bodies are never logged.
- **OpenAPI** lists the success under the media type, with no schema, as
  OpenAPI 3.1 writes a binary body. `x-jadpo-sends` names the `Output` and the
  field, and `Content-Length` is one of its `headers`. Failures are listed as
  any route's are. An importer gives the operation no `output:`, since it reads
  only JSON.
- **The API record** says `sends application/pdf` under the route, and the
  `Output` keeps its entry. A route that starts or stops sending raw bytes, or
  sends another media type, breaks whoever reads it, so it needs an acceptance:
  `ApiChange.media_type_changed` ([Breaking changes](/docs/the-public-api-record.md#breaking-changes)).

## Answers that never change

Nothing caches a route's answer unless the route says so. `cache: immutable`
says the answer never changes, as a scan of a signed deed doesn't.
The route's 200 then carries `Cache-Control: public, max-age=31536000,
immutable`, so a browser or a CDN keeps it for a year and doesn't ask again. It
goes with `sends:` or with JSON. A failure never carries the header, so a 404
for a deed not scanned yet isn't kept.

Anything else is `route.cache_invalid`:

- **Only `immutable`.** It is the one thing `cache:` says.
- **Only a `GET`.** No other method's answer can be kept.
- **Only a route anyone may call.** A public cache would hand what one caller
  was sent to anyone who asked, so a route needing a signed-in caller can't say
  it.
- **Only an answer of 200.** Any other `status:` is refused.
- **No personal data.** No erasure reaches a public cache, so what the route
  runs can't give any, directly or through what it calls, and its `output:`
  can't say it holds any. It counts as
  [the personal-data report](/docs/the-personal-data-report.md#where-personal-data-goes)
  counts: a field marked `personal:`, and a standard type that is personal data
  whatever holds it, such as `PhoneNumber` or `Email`, on a field with no mark as much as on its
  own ([What the compiler checks](/docs/personal-data-checks.md)).

OpenAPI says `x-jadpo-cache: immutable` on the operation and lists the header
on the 200, and the API record says `cache immutable` under the route, as the
record below shows. Adding it or taking it away breaks no client, so neither
needs an acceptance.

```jadpo
// Generated by Jadpo from the routes of API main; update it with `jadpo api update`, never by hand.
record api 1

GET /deeds/{deed_ref: Text { pattern: "^D[0-9]{6}$" }}
    authentication none
    output DeedScan
    sends application/pdf
    status 200
    cache immutable
    fails deed_not_found

GET /openapi.json
    generated openapi
    authentication none
    status 200

output DeedScan
    pdf: Bytes

failure deed_not_found
    status 404
    message "No deed has this reference."
```
