purescript-httpurple/docs/Requests.md
2022-06-11 17:56:56 +01:00

4.7 KiB

Requests

This section describes how to work with requests bodys. For information about routing see the routing docs.

TOC

  1. tl;dr
  2. introduction
  3. json request parsing
  4. data validation
  5. business logic
  6. output json

tl;dr

router { route: Home, method: Post, body } = usingCont do
    jsonRequest :: MyRequest <- fromJson Argonaut.jsonDecoder body -- parse the json input
    input :: MyValidatedInput <- fromValidated validateMyRequest jsonRequest -- validate the input data
    output :: MyOutput <- lift $ doSomethingAndReturnAff input -- do your business logic
    ok' jsonHeaders $ toJson Argonaut.jsonEncoder output -- return output as json

Introduction

A typical micro-service scenario looks like this:

  1. You get a json request, so the first thing you need to do is to parse your json request format and return a bad request if it doesn't match your format.
  2. Then you do some kind of data/business validation of the input. Is the field email really an email? So you want to do this semantic validation and return a bad request (with some error code) in case it is invalid.
  3. Now since you have the validated input data, you can do your actual logic and produce some output data.
  4. Finally you want to dump this output data as json

HTTPurple 🪁 provides a very simple way of handling this scenario based on continuations. Let's go through each step:

1. Json request parsing

HTTPurple 🪁 provides a fromJson method that makes json parsing of your body super simple. fromJson is library agnostic, so you can use argonaut using the argonaut-driver, use yoga-json using the yoga-json-driver or write your own json driver. If you don't know which one to use, I would recommend to go with the argonaut driver.

# for argonaut install the argonaut driver:
spago install httpurple-argonaut

# for yoga-json install the yoga-json driver:
spago install httpurple-yoga-json

fromJson returns a continuation, so just start your handler implementation with usingCont and then use fromJson with Argonaut.jsonDecoder or Yoga.jsonDecoder:

router { route: Home, method: Post, body } = usingCont do
    jsonRequest :: MyRequest <- fromJson Argonaut.jsonDecoder body

As you can see, really simple. This parses the body into your format and returns a bad request if it fails. If you need to customise the bad request, there is fromJsonE which allows you to pass a custom bad request handler.

2. Data validation

Next, we will want do validate our json data. Or put in other words, we want to transform the weakly typed json model into a strongly-typed internal data model.

fromValidated takes a validation function of the shape input -> Either error validated:

router { route: Home, method: Post, body } = usingCont do
    jsonRequest :: MyRequest <- fromJson Argonaut.jsonDecoder body
    input :: MyValidatedInput <- fromValidated validateMyRequest jsonRequest

If you need a custom bad request handler, you can use fromValidatedE which takes an bad request handler as second parameter.

3. Your business logic

Now that you got your validated input, you can pass it to your business logic. There is one caveat though, we are currently in a continuation, so you will need to lift your Aff returning function into the continuation monad:

router { route: Home, method: Post, body } = usingCont do
    jsonRequest :: MyRequest <- fromJson Argonaut.jsonDecoder body
    input :: MyValidatedInput <- fromValidated validateMyRequest jsonRequest
    output :: MyOutput <- lift $ myAffReturningFunction input

4. Output json

Finally you can return your json. Use the toJson function which takes the json driver and your output your data as json:

router { route: Home, method: Post, body } = usingCont do
    jsonRequest :: MyRequest <- fromJson Argonaut.jsonDecoder body
    input :: MyValidatedInput <- fromValidated validateMyRequest jsonRequest
    output :: MyOutput <- lift $ doSomethingAndReturnAff input
    ok' jsonHeaders $ toJson Argonaut.jsonEncoder output

If your business logic output is strongly typed, I would write a transformer function to a weakly typed json model instead of writing custom json encoders:

    ok' jsonHeaders $ toJson Argonaut.jsonEncoder $ transformToApi output