Sap v0.0.3 Sap View Source

Sap is a toolkit for Plug applications to accept and respond to HTTP requests by using a decision tree built with combinators.

Making a decision with combinators

In most Plug applications, there is some sort of match and/or dispatch process that takes place for an individual request. A request will come in, a set of pattern matching statements created will be compared against the requested path, and if there’s a match, a function will be called to send the client a response. Here, there is a clear separation between matching/filtering a request and creating a response.

With Sap, the basic idea remains the same, but the way a request is accepted and a response is created is slightly different. Combinators, a fancy name for simple functions meant to be composed with one another, are tasked with one of two goals:

  • filter requests based on some criteria
  • build up a response

The power of these functions comes from the ability to compose them in any order. There is no clear separation of each type of combinators, so a filtering combinator can be used both before and after a building combinator and vice versa.

Example

To help demonstrate this, let’s walk through a small example.

def app do
  choose [
    get ~>> resp_json ~>> choose [
      path("/body2") ~>> ok("{\"data\": \"body 2\"}"),
      path("/body1") ~>> ok("{\"data\": \"body 1\"}")
      ]
    ]
end

Sap.serve(app: app)
# or Plug.Adapters.Cowboy.http Sap, [app: app], []

A few combinators are used, both filtering and building, to define our application, with all of them working together to create a decision tree for dealing with requests.

choose/1 allows for multiple options at a decision tree node. get/0 only permits HTTP GET requests in the rest of a node’s definition. resp_json/0 sets the response header for the rest of a node to application/json. path/1 filters out requests that do not match a given path. ok/1 sets the response status code of 200 Ok and the given response body.

With the above example, all POST, PUT, PATCH, etc. requests would not be handled. There was an explicit declaration that only GET requests are allowed since there is only one option in the first choose/1 list. At the second choose/1 list, the application has two options, /body1 and /body2.

These combinators work together through a Sap.Context struct that is passed between them that holds a Plug.Conn and a status for the current decision path. As long as the status is :ok, further combinators can affect the decision, but once the status is :error, the decision path effectively ends, giving way to the next option path.

Link to this section Summary

Functions

Callback implementation for Plug.call/2

Callback implementation for Plug.init/1

Convenience function to start a Plug adapter with Sap as the plug

Called when an application is started

Link to this section Functions

Callback implementation for Plug.call/2.

Callback implementation for Plug.init/1.

Link to this function serve(opts \\ [], adapter_opts \\ []) View Source
serve(Keyword.t(), Keyword.t()) :: {:ok, pid()} | {:error, term()}

Convenience function to start a Plug adapter with Sap as the plug.

Options

  • :app - (required) - the Sap app decision tree. An ArgumentError will be raised if this option is not passed.

  • :scheme - desired scheme on which the Plug adapter should attach (defaults to :http)

Adapter Options

See the Plug docs for a detailed list of possible adapter options.

Called when an application is started.

This function is called when an application is started using Application.start/2 (and functions on top of that, such as Application.ensure_started/2). This function should start the top-level process of the application (which should be the top supervisor of the application’s supervision tree if the application follows the OTP design principles around supervision).

start_type defines how the application is started:

  • :normal - used if the startup is a normal startup or if the application is distributed and is started on the current node because of a failover from another node and the application specification key :start_phases is :undefined.
  • {:takeover, node} - used if the application is distributed and is started on the current node because of a failover on the node node.
  • {:failover, node} - used if the application is distributed and is started on the current node because of a failover on node node, and the application specification key :start_phases is not :undefined.

start_args are the arguments passed to the application in the :mod specification key (e.g., mod: {MyApp, [:my_args]}).

This function should either return {:ok, pid} or {:ok, pid, state} if startup is successful. pid should be the PID of the top supervisor. state can be an arbitrary term, and if omitted will default to []; if the application is later stopped, state is passed to the stop/1 callback (see the documentation for the c:stop/1 callback for more information).

use Application provides no default implementation for the start/2 callback.

Callback implementation for Application.start/2.