# flare.grpc.server

> A module of flare_mlake's API.

``flare.grpc.server`` -- gRPC server adapter on the HTTP/2 reactor.

A gRPC unary call maps onto a single HTTP/2 stream:

* Request HEADERS: ``:method=POST``, ``:scheme=http(s)``,
  ``:path=/<service>/<method>``, ``content-type=application/grpc``
  (or one of the ``+proto`` / ``+json`` flavours),
  ``te: trailers``, plus optional ``grpc-encoding`` /
  ``grpc-accept-encoding`` / ``grpc-timeout`` / application
  metadata.
* Request DATA: zero or more length-prefix-message (LPM) frames
  containing the serialized request bodies.
* Response HEADERS: ``:status=200``, ``content-type=application/
  grpc``, optional ``grpc-encoding``.
* Response DATA: zero or more LPM frames carrying the serialized
  reply bodies.
* Response trailing HEADERS: ``grpc-status`` (REQUIRED), optional
  ``grpc-message``, optional trailing metadata.

This module exposes the adapter that performs the validation +
LPM stitching + status-code mapping, so a unary handler reads
the decoded request bytes and writes either ``Ok(response_bytes)``
or a typed :class:`GrpcStatus` error and the adapter takes care
of the rest.

Public surface:

* :class:`GrpcCallContext` -- per-call view: full path, deadline
  (parsed from ``grpc-timeout``), initial metadata, accept-
  encoding hint.
* :class:`GrpcUnary` -- the user handler trait. ``serve_unary(ctx,
  request_bytes) -> (response_bytes, status, trailing_metadata)``.
* :class:`GrpcServerAdapter` -- bridges one HTTP/2 stream to one
  :class:`GrpcUnary` call. Steps:
  1. validate request HEADERS;
  2. accumulate request DATA bytes until END_STREAM;
  3. decode LPM frames -> request bytes;
  4. invoke the unary handler;
  5. encode response bytes as LPM frame;
  6. emit response HEADERS + DATA + trailing HEADERS.

The adapter is sans-I/O at the codec boundary: byte streams in,
HTTP/2 wire bytes (HEADERS frame field section + DATA payload +
trailing HEADERS field section) out. Networking + flow control
is the Http2Connection's job.

References:
- gRPC PROTOCOL-HTTP2 spec.
- RFC 9113 (HTTP/2) §4 (frames) + §6.10 (TRAILERS marker).

## Structs

### `GrpcRequestHeaders`

```mojo
struct GrpcRequestHeaders
```

Typed carrier for the H2 request-HEADERS field set the gRPC adapter consumes.

The ``method`` / ``path`` / ``content_type`` / ``te`` fields are
REQUIRED on every well-formed gRPC call; the optional fields
(``timeout`` -- ``grpc-timeout``, ``accept_encoding`` --
``grpc-accept-encoding``) carry the empty :class:`Optional` when
the client omits them. ``initial_metadata`` is the
application-visible subset (``grpc-`` reserved keys stripped).

The H2 driver builds this carrier from the parsed
``H2RequestHeaders`` field set and hands it to
:func:`parse_request_headers` or :func:`run_unary_call`. The
typed shape collapses the prior seven-positional-string
signature into a single argument and lets the driver build
each field with the right ``Optional[String]`` semantics.

Implements: `Copyable`, `Movable`

Fields:

- `method: String`
- `path: String`
- `content_type: String`
- `te: String`
- `timeout: Optional[String]`
- `accept_encoding: Optional[String]`
- `encoding: Optional[String]`
- `initial_metadata: GrpcMetadata`

### `GrpcCallContext`

```mojo
struct GrpcCallContext
```

View of the per-call HTTP/2 request state visible to the application handler.

The path field is the gRPC method name (``/svc/method``); the
deadline is the absolute microsecond timestamp at which the
server should abandon the call (``0`` means "no deadline
specified"). Initial metadata is the application-visible
subset of the request HEADERS (``grpc-`` reserved keys are
stripped before the handler sees them).

Implements: `Copyable`, `Movable`

Fields:

- `path: String`
- `deadline_us: UInt64`
- `initial_metadata: GrpcMetadata`
- `accept_encoding: String`

### `GrpcUnaryReply`

```mojo
struct GrpcUnaryReply
```

Typed return value for a unary gRPC handler.

Replaces the prior ``Tuple[List[UInt8], GrpcStatus, GrpcMetadata]``
so a handler's call site reads as ``return GrpcUnaryReply.ok(body)``
or ``return GrpcUnaryReply.err(GrpcStatus.err(...))`` instead of a
positional 3-tuple. The :func:`run_unary_call` driver always emits
an empty response body for non-OK replies; ``body`` is ignored in
that case (the spec carries the status on the trailers, not the
payload).

The two factory methods (:func:`ok` and :func:`err`) handle the
metadata-default ergonomics so a handler that does not attach
trailing metadata can omit the argument.

Implements: `Copyable`, `Movable`

Fields:

- `body: List[UInt8]`
- `status: GrpcStatus`
- `trailing_metadata: GrpcMetadata`

#### `ok`

```mojo
def ok(var body: List[UInt8], var trailing_metadata: GrpcMetadata = GrpcMetadata()) -> Self
```

Build an OK reply carrying ``body`` and the (optional) trailing metadata. ``GrpcStatus.ok()`` is filled in for the caller.

#### `err`

```mojo
def err(var status: GrpcStatus, var trailing_metadata: GrpcMetadata = GrpcMetadata()) -> Self
```

Build a non-OK reply carrying ``status`` plus optional trailing metadata. The body is always empty; the trailers carry the status code + message.

### `GrpcCallOutcome`

```mojo
struct GrpcCallOutcome
```

Output of one full unary call -- the bytes the H2 driver queues onto the response stream.

* ``response_data`` is the encoded LPM frame for the response
  body (empty on non-OK status; the trailers carry the status).
* ``status`` is the gRPC status code the trailer must carry.
* ``trailing_metadata`` is the trailing HEADERS field set
  excluding ``grpc-status`` / ``grpc-message`` (those are
  framework-managed; see :func:`emit_trailing_headers`).

Implements: `Copyable`, `Movable`

Fields:

- `response_data: List[UInt8]`
- `status: GrpcStatus`
- `trailing_metadata: GrpcMetadata`
- `encoding: String`

### `GrpcService`

```mojo
struct GrpcService[H: GrpcUnary & Copyable & Deinitable]
```

Adapt a :class:`GrpcUnary` handler into a plain :trait:`Handler` so it serves over the unified :class:`flare.http.HttpServer` H2 reactor path -- no bespoke per-RPC glue.

Mount it like any other handler::

    var svc = GrpcService(EchoHandler())
    var server = HttpServer.bind(addr)
    server.serve(svc)   # over h2c / h2

One H2 stream maps to one unary call: the reactor hands this
adapter the assembled :class:`Request` (HEADERS + DATA), the adapter
runs :func:`run_unary_call`, and returns a :class:`Response` whose
trailers carry ``grpc-status``. The inner handler is copied per call
(``H: Copyable``) so the immutable ``serve`` borrow holds while
``serve_unary`` mutates its own copy -- unary handlers are config-
only in practice, so the copy is cheap.

Deadlines: when the client sends ``grpc-timeout`` the adapter
enforces it as a wall-clock ceiling on the handler invocation and
returns ``DEADLINE_EXCEEDED`` if the handler overruns.

Deadline enforcement is a post-hoc elapsed check, not a
cooperative mid-call cancel (the handler runs to completion, then we
compare elapsed vs budget), so a runaway handler still burns
its full time before the status flips. Cancelling it promptly would
need a :class:`flare.http.Cancel` threaded into a cancel-aware
``GrpcUnary`` variant and flipped from a deadline timer.

Implements: `Copyable`, `Handler`, `Movable`

Fields:

- `handler: H`

#### `BodyType`

```mojo
comptime BodyType = InlineBody
```

The ``Body`` impl this handler authors its response with.

Defaults to :struct:`InlineBody` so plain handlers that return
``Response`` conform without declaring it. Override in a handler that
returns a typed ``ResponseImpl[SomeBody]``.

#### `serve`

```mojo
def serve(self, req: Request) -> Response
```

## Traits

### `GrpcUnary`

```mojo
undefined
```

Single-method gRPC handler.

The handler receives the decoded request bytes (already
LPM-stitched + decompressed) and returns a typed
:class:`GrpcUnaryReply` carrying the response body + status +
trailing metadata. Returning a non-OK reply causes the adapter
to emit an empty response body + the trailers; the response
bytes are ignored in that case.

Implements: `Movable`

#### `serve_unary`

```mojo
def serve_unary(mut self, ctx: GrpcCallContext, request_bytes: Span[UInt8]) -> GrpcUnaryReply
```

## Functions

### `parse_request_headers`

```mojo
def parse_request_headers(headers: GrpcRequestHeaders) -> GrpcCallContext
```

Validate the H2 request HEADERS for a gRPC call and build the :class:`GrpcCallContext`.

Validation surface (all RFC + gRPC PROTOCOL-HTTP2 MUSTs):

* ``:method`` MUST be ``POST``.
* ``:path`` MUST start with ``/`` and contain at least one
  additional ``/`` segment (``/<service>/<method>``).
* ``content-type`` MUST be ``application/grpc[+flavor]``.
* ``te`` MUST contain the ``trailers`` token. Without it the
  client wouldn't read the trailing HEADERS, so the call has
  no way to learn the status; the spec promotes the missing
  ``te: trailers`` to a connection-level fail-closed.

The optional ``grpc-timeout`` is parsed into a microsecond
deadline; missing / unparseable timeouts surface as ``0``
(no deadline) so partial deployments don't fail closed.

### `stitch_request_data`

```mojo
def stitch_request_data(request_data: Span[UInt8], encoding: String = "", max_decompressed_bytes: Int = Int(16777216)) -> List[UInt8]
```

Stitch one or more LPM frames out of the accumulated request DATA bytes into a single contiguous payload.

Returns the concatenated payloads (in order). gRPC unary
calls SHOULD only emit a single LPM frame, but the spec
permits a server to receive multiple back-to-back frames
inside one call (a streaming client packing requests through
a unary stub is the example shape); the adapter handles both.

Compressed frames (flag bit 0 set) are decompressed using the
call's ``grpc-encoding`` (``gzip`` / ``deflate``). A compressed
frame with no / unsupported ``grpc-encoding`` raises.

``max_decompressed_bytes`` bounds the whole call, not each frame:
it was a per-frame cap, so a body of many small compressed frames,
each inflating to just under 16 MiB, stitched into one payload of
any size.

Raises on:
* truncated LPM frame (need-more-data at the end of the
  request body),
* compressed flag set with no negotiated / supported encoding,
* decompressed output past ``max_decompressed_bytes``.

### `encode_unary_response`

```mojo
def encode_unary_response(response_bytes: List[UInt8], mut out: List[UInt8], encoding: String = "")
```

Append a single LPM frame wrapping ``response_bytes`` to ``out``.

When ``encoding`` is ``gzip`` / ``deflate`` and the payload is at
least :data:`GRPC_COMPRESS_MIN_BYTES`, the payload is compressed
and the frame's flag byte is set (bit 0). Otherwise an
uncompressed frame (flag 0) is emitted. The caller is responsible
for advertising the chosen codec on the response ``grpc-encoding``
header (see :func:`_response_from_outcome`).

The caller owns the buffer and may reuse the same
``List[UInt8]`` across responses so the underlying
allocation amortises across the stream. The encoder appends
only; it never reads from or truncates the existing contents.

### `emit_trailing_headers_status`

```mojo
def emit_trailing_headers_status(status: GrpcStatus) -> List[Tuple[String, String]]
```

Return the framework-controlled trailer entries for a completed gRPC call.

Always emits ``grpc-status`` (the wire-level integer outcome).
Emits ``grpc-message`` when ``status.message`` is non-empty
(clients use it for diagnostics only). Emits
``grpc-status-details-bin`` when ``status.details`` carries a
payload, RFC 4648 §4 base64-encoded per gRPC PROTOCOL-HTTP2.

The caller (the H2 trailer encoder) appends the application
trailing metadata after these framework-owned entries; this
helper does not look at :class:`GrpcMetadata`.

Returning ``List[Tuple[String, String]]`` keeps the trailer
emitter sans-I/O: the H2 driver decides how to serialise the
pairs onto a HEADERS frame (HPACK literal-without-indexing,
QPACK, h3 SETTINGS-aware, etc.).

### `run_unary_call`

```mojo
def run_unary_call[H: GrpcUnary](mut handler: H, headers: GrpcRequestHeaders, request_data: Span[UInt8]) -> GrpcCallOutcome
```

End-to-end driver: validate headers, stitch LPM, invoke the user handler, encode the response.

The HTTP/2 driver calls this once per stream; success
produces a :class:`GrpcCallOutcome` whose bytes go on the
DATA frame and whose status / trailing metadata go on the
trailing HEADERS frame.

The driver itself never raises -- every failure mode
(HEADERS validation, truncated/compressed LPM, handler
raising) is folded into a typed :class:`GrpcCallOutcome`
with the right ``grpc-status``:

* HEADERS validation failure ->
  ``GRPC_STATUS_INVALID_ARGUMENT`` (the gRPC spec maps client
  malformed input to this code).
* LPM stitch failure (truncated frame, compressed without a
  negotiated encoding) -> ``GRPC_STATUS_INVALID_ARGUMENT``.
* Handler ``raises`` -> ``GRPC_STATUS_INTERNAL`` with the fixed
  message ``"handler raised"``. The raise text stays on the
  server: the failure is a server-side bug, not client input,
  and the text can carry anything the exception held.

## Aliases

### `CONTENT_TYPE_GRPC`

```mojo
comptime CONTENT_TYPE_GRPC = String("application/grpc")
```

Canonical gRPC content-type. The adapter also accepts the flavored variants ``application/grpc+proto`` / ``+json`` / ``+thrift`` -- any string starting with this prefix and an optional ``+<flavor>`` segment.

### `GRPC_COMPRESS_MIN_BYTES`

```mojo
comptime GRPC_COMPRESS_MIN_BYTES = Int(64)
```

Response payloads at or above this size are eligible for message-level compression when the client advertises a codec via ``grpc-accept-encoding``. Below it the gzip/deflate header overhead outweighs the saving, so the frame ships uncompressed (flag 0). This is a fixed cutoff, not an adaptive ratio probe.
