Conditional redirect from endpoint handlers?

Hi!

I’m trying to do a redirect from an endpoint handler and so far am not having much luck doing it. Here’s my use case.

The app is a flashcards app for memorizing various information. There are, among others, two endpoints: `POST /decks`, to create a new deck of cards, and `GET /card_type/{id}/edit`, to edit a card type.

`/decks` creates a new deck and also a new default card type. This is done from the `handleSuccess` method. Now, at the end, I want the user to be redirected to the `/card_type/{id}/edit`, using the `{id}` created by the `/decks` handler. Here’s the code:

val createDeckEndpoint = endpoint
  .post.in("decks").in(formBody[Seq[(String, String)]]).out(stringBody)
  .out(header(Header.contentType(MediaType.TextHtml)))

val serverCreateDeckEndpoint = createDeckEndpoint
  .handleSuccess:
    (body: Seq[(String, String)]) =>
      val deck = /* Some logic to build the deck model */
      val deckId = service.decks.createDeck(deck)

      // Create the first card type
      val cardType = /* Some logic to build the CardType model */
      val cardTypeId = service.cardTypes.createCardType(cardType)

      // Redirect the user to the edit page for the newly created card type: /card_type/{cardTypeId}/edit

Does anyone have an idea how can I accomplish such a redirect?

I think you’ll need to add an output which is a location header, something like:

.out(header[String](HeaderNames.Location))

Then in the endpoint’s logic, you’ll need to provide the string value for the header

So you mean the handleSuccess will return a string “/card_type/{id}/edit”, and it’ll be automatically interpreted as a value for the header?

Is it possible to set the header output type from the handler itself, conditional on the request body? Or it must be set outside the handler?

The server logic produces values as specified by the endpoint description, and maps them to headers/body. If you want to do conditional redirects, simply include an optional header in the output description:

.out(header[Option[String]](HeaderNames.Location))

The above means that the Option[String] that you return from your server logic will be mapped to the value of the location header (if present).

You cannot set response values in any other way

1 Like

Another somewhat related question:

val editCardTypeEndpoint = endpoint
  .get.in("card_type" / path[Long] / "edit").out(stringBody)
  .out(header(Header.contentType(MediaType.TextHtml)))
  .out(header(Headers.hxPushUrl, s"/card_type/$cardTypeId/edit"))

Here I have an endpoint that is supposed to be accepting both AJAX and ordinary requests. In case of AJAX requests, I need to push a corresponding URL to the browser history, with the custom `Headers.hxPushUrl` header.

Is there a way to transfer the `in` parameter `path[Long]` into the `out` header’s `cardTypeId` variable? Note that in this case, I also emit the HTML body, so the trick with returning the header value from the body won’t work…

Ah, nevermind, I realised outputs may be chained into a tuple:

val editCardTypeEndpoint = endpoint
  .get.in("card_type" / path[Long] / "edit").out(stringBody)
  .out(header(Header.contentType(MediaType.TextHtml)))
  .out(header[String](Headers.hxPushUrl))

val serverEditCardTypeEndpoint = editCardTypeEndpoint
  .handleSuccess:
    (cardTypeId: Long) =>
      val cardType = service.cardTypes.getCardTypeById(cardTypeId)
      val body = html.upsertCardType(Some(cardType)).toString
      (body, s"/card_type/$cardTypeId/edit")