# Handling Queries over HTTP

A query is asked with the method `QUERY`, which HTTP defines in RFC 10008. Like `GET`, it is safe, so it changes nothing, but like `POST`, it carries a body, so a query takes its input from the body, as a command does. To answer a query over HTTP, define a request type that derives `Deserialize` for the body, and a handler of axum that builds the query from it and answers it.

Like the handler of a command, it takes the body with `Strict`, the user, and whatever else it needs from other extractors. The request type describes the body:

```rust
#[derive(Default, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct ListBooksRequest {
  only_available: bool,
  limit: Option<usize>,
}
```

With `#[serde(default)]`, a field that the body leaves out takes its default, so a caller may send `{}`, rather than having a missing field refused (see **[Validating Requests](/docs/architecturekit/rust/handling-commands-over-http#validating-requests)**). As for a command, the request type describes the body, and the query is what the application works with, so the query needs no `Deserialize`. The handler builds the query from the request type, so it may check the input, and add what the body does not hold, such as the user or a value of the path, as `handle_borrow_book` adds the reader to the command (see **[Handling Commands over HTTP](/docs/architecturekit/rust/handling-commands-over-http)**).

**An item describes what the view stores, not what a caller sees** (see **[Defining Views](/docs/architecturekit/rust/defining-views)**). So to answer with JSON, define a response type that derives `Serialize`, as the counterpart of the request types that commands use (see **[Handling Commands over HTTP](/docs/architecturekit/rust/handling-commands-over-http)**), and map the items to it. Here, the revision of a book goes along as `eventId`, so that a caller can send it back as `expectedEventId`:

```rust
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BookBody {
  id: String,
  title: String,
  author: String,
  is_borrowed: bool,
  borrowed_until: String,
  event_id: Option<Revision>,
}

impl From<BookItem> for BookBody {
  fn from(book: BookItem) -> Self {
    Self {
      id: book.id,
      title: book.title,
      author: book.author,
      is_borrowed: book.is_borrowed,
      borrowed_until: book.borrowed_until,
      event_id: book.revision,
    }
  }
}
```

The handler answers the query with the function that reads the view, such as `list_books` (see **[Defining Queries](/docs/architecturekit/rust/defining-queries)**), maps what it returns, and wraps the result in a `QueryAnswer`:

```rust
use architecturekit::httpapi::QueryAnswer;

pub async fn handle_list_books(
  State(app): State<App>,
  _: User,
  Strict(request): Strict<ListBooksRequest>,
) -> Result<QueryAnswer<Vec<BookBody>>, HttpError> {
  let query = ListBooks { only_available: request.only_available, limit: request.limit };
  let books = list_books(&app.catalog_projection.catalog, query).await?;

  Ok(QueryAnswer::new(books.into_iter().map(BookBody::from).collect()))
}
```

The view in memory never fails while it is read, so its error is `Infallible`, which `?` turns into an `HttpError` that never occurs. A view in a database hands on its error, which `?` answers by its category. Then route the handler with the `query` function of the kit, as `get` and `post` of axum route theirs:

```rust
use architecturekit::httpapi::query;

let router = Router::new().route("/api/books", query(handle_list_books));
```

axum names no filter for the method `QUERY` before its version 0.9, so the route that `query` makes takes every method, but only `QUERY` reaches the handler: every other method is answered before it, with `405 Method Not Allowed` and the header `Allow: QUERY`. A handler that answers in a format of its own may use another method, though, for example `GET` for a download or a CSV export (see **[Answering Queries in Your Own Format](/docs/architecturekit/rust/handling-queries-over-http#answering-queries-in-your-own-format)**).

The caller sends the input as JSON, as for a command:

```shell
curl -X QUERY http://localhost:8080/api/books \
  -H "Authorization: Bearer …" \
  -H "Content-Type: application/json" \
  -d '{"onlyAvailable":true}'
```

The handler determines the user, and then `Strict` decodes the body by the same rules as for a command (see **[Validating Requests](/docs/architecturekit/rust/handling-commands-over-http#validating-requests)**), before the handler builds the query. So a body that does not claim to be JSON, that is too large, or that is not valid JSON or does not fit the request type is answered with `415`, `413`, or `400`, and the same message as for a command.

A query without input, such as one that takes everything it needs from the path, takes `NoBody` instead of `Strict`, so that the caller sends no body, or `{}`. As for a command without a body, a request that a browser sends from another origin is then answered with `403 Forbidden` (see **[Handling Commands Without a Body](/docs/architecturekit/rust/handling-commands-over-http#handling-commands-without-a-body)**). Here, the handler answers with `get_book` (see **[Defining Queries](/docs/architecturekit/rust/defining-queries)**):

```rust
pub async fn handle_get_book(
  State(app): State<App>,
  Path(book_id): Path<String>,
  _: User,
  _: NoBody,
) -> Result<QueryAnswer<BookBody>, HttpError> {
  let book = get_book(&app.catalog_projection.catalog, GetBook { book_id }).await?;

  Ok(QueryAnswer::new(BookBody::from(book)))
}
```

```rust
let router = Router::new().route("/api/books/{id}", query(handle_get_book));
```

`QueryAnswer` answers with `200 OK` and the result as JSON, with `Cache-Control: no-store`, so that no cache keeps it (see **[Reading Your Own Writes over HTTP](/docs/architecturekit/rust/reading-your-own-writes-over-http)**). The result is encoded with serde, so an empty `Vec` is answered as an empty list, `[]`, an empty map as an empty object, `{}`, and `None` as `null`. **A caller gets a list or an object, whether it holds anything or not.** A result that can not be encoded, for example a map whose keys are tuples, is a mistake in the code, and is answered with `500 Internal Server Error` and logged, like any other internal failure. Errors and panics are answered as for commands (see **[Handling Commands over HTTP](/docs/architecturekit/rust/handling-commands-over-http)**).

*Note that the function that reads the view, such as `list_books`, receives the view and the query, but neither the request nor the user. Whatever it needs from them, the handler puts into the query, which matters once a caller reads its own writes (see **[Reading Your Own Writes over HTTP](/docs/architecturekit/rust/reading-your-own-writes-over-http)**).*

## Answering Queries in Your Own Format

To answer in a format of your own, return whatever axum can turn into a response, and keep `HttpError` for the failures. Such a handler may use another method than `QUERY`, for example `GET` for a CSV export that a browser can download from a link. A `GET` request carries no body, so the handler takes its input from the URL instead, with the `Query` extractor of axum:

```rust
use std::fmt::Write;

use axum::{extract::Query, http::header, response::IntoResponse, routing::get};

#[derive(Deserialize)]
pub struct ExportBooksParameters {
  #[serde(default)]
  available: bool,
}

pub async fn handle_export_books(
  State(app): State<App>,
  _: User,
  Query(parameters): Query<ExportBooksParameters>,
) -> Result<impl IntoResponse, HttpError> {
  let query = ListBooks { only_available: parameters.available, limit: None };
  let books = list_books(&app.catalog_projection.catalog, query).await?;

  let mut csv = String::from("id,title,author\n");
  for book in books {
    let _ = writeln!(csv, "{},{},{}", book.id, book.title, book.author);
  }

  Ok(([(header::CONTENT_TYPE, "text/csv")], csv))
}
```

```rust
let router = Router::new().route("/api/books.csv", get(handle_export_books));
```

A failure is still an `HttpError`, which answers with the same status codes and messages as for a query in the default format, says `Cache-Control: no-store`, and logs the same way.

*Note that this sketch writes the values as they are. A real export escapes values that contain commas, quotes, or line breaks, for example with the csv crate.*

*Note that a `GET` request that takes `NoBody` passes from any origin, unlike a `QUERY` request that takes it, since `GET` must not change anything (see **[Handling Commands Without a Body](/docs/architecturekit/rust/handling-commands-over-http#handling-commands-without-a-body)**).*

## Asking for Several Items at Once

Since a `QUERY` request carries a body, the input of a query may hold more than a URL could, for example a list of books to check at once. Here, the answer tells for every book whether it is available:

```rust
use std::collections::HashMap;

use architecturekit::KeyedView;

pub struct CheckAvailability {
  pub book_ids: Vec<String>,
}

pub async fn check_availability<V: KeyedView<str, BookItem>>(
  catalog: &V,
  query: &CheckAvailability,
) -> Result<HashMap<String, bool>, V::Error> {
  let mut is_available = HashMap::with_capacity(query.book_ids.len());
  for book_id in &query.book_ids {
    let book = catalog.get(book_id).await?;
    let _ = is_available.insert(book_id.clone(), book.is_some_and(|book| !book.is_borrowed));
  }

  Ok(is_available)
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CheckAvailabilityRequest {
  book_ids: Vec<String>,
}

pub async fn handle_check_availability(
  State(app): State<App>,
  _: User,
  Strict(request): Strict<CheckAvailabilityRequest>,
) -> Result<QueryAnswer<HashMap<String, bool>>, HttpError> {
  let query = CheckAvailability { book_ids: request.book_ids };
  let is_available = check_availability(&app.catalog_projection.catalog, &query).await?;

  Ok(QueryAnswer::new(is_available))
}
```

```rust
let router = Router::new().route("/api/check-availability", query(handle_check_availability));
```

## Reporting Missing Items

If the function that reads the view returns `SingleError::NoItems`, as `single` does if no item matches, `?` answers with `404 Not Found`, and the message `not found`, since nothing says what was not found. The error is logged at level `info`, as for `401` and `409`. That is how `handle_get_book` above answers a book that does not exist. To look up a single item by its key, hand what `get` of a `KeyedView` returns to `QueryAnswer::found`, which answers `None` the same way:

```rust
pub async fn handle_get_book(
  State(app): State<App>,
  Path(book_id): Path<String>,
  _: User,
  _: NoBody,
) -> Result<QueryAnswer<BookBody>, HttpError> {
  let book = app.catalog_projection.catalog.get(&book_id).await?;

  Ok(QueryAnswer::found(book.map(BookBody::from)))
}
```

`single` fails with `SingleError::TooManyItems` if several items match, which `?` answers with `500 Internal Server Error`, logged at level `error`, since the caller can not fix it.

To say what was not found, return `HttpError::not_found` with what is missing. Such an error is written for the caller, so it is the message, here `not found: book 42`, and it is not logged:

```rust
let Some(book) = app.catalog_projection.catalog.get(&book_id).await? else {
  return Err(HttpError::not_found(format!("book {book_id}")));
};
```
