All Projects → weavejester → Ataraxy

weavejester / Ataraxy

Licence: other
A data-driven Ring routing and destructuring library

Programming Languages

clojure
4091 projects
ring
36 projects

Projects that are alternatives of or similar to Ataraxy

Reitit
A fast data-driven router for Clojure/Script
Stars: ✭ 892 (+377.01%)
Mutual labels:  data-driven, routing
Navi
🧭 Declarative, asynchronous routing for React.
Stars: ✭ 2,069 (+1006.42%)
Mutual labels:  routing
Mdtable
A data-driven UITableView framework
Stars: ✭ 153 (-18.18%)
Mutual labels:  data-driven
Rayo.js
Micro framework for Node.js
Stars: ✭ 170 (-9.09%)
Mutual labels:  routing
Redux Tower
Saga powered routing engine for Redux app.
Stars: ✭ 155 (-17.11%)
Mutual labels:  routing
Flow builder
Flutter Flows made easy! A Flutter package which simplifies flows with a flexible, declarative API.
Stars: ✭ 169 (-9.63%)
Mutual labels:  routing
Nextjs Dynamic Routes
[Deprecated] Super simple way to create dynamic routes with Next.js
Stars: ✭ 145 (-22.46%)
Mutual labels:  routing
Fault tolerant router
A daemon, running in background on a Linux router or firewall, monitoring the state of multiple internet uplinks/providers and changing the routing accordingly. LAN/DMZ internet traffic is load balanced between the uplinks.
Stars: ✭ 182 (-2.67%)
Mutual labels:  routing
Routerify
A lightweight, idiomatic, composable and modular router implementation with middleware support for the Rust HTTP library hyper.rs
Stars: ✭ 173 (-7.49%)
Mutual labels:  routing
El Form Renderer
🎩A data-driven dynamic and complex form solution
Stars: ✭ 163 (-12.83%)
Mutual labels:  data-driven
Libosmscout
Libosmscout is a C++ library for offline map rendering, routing and location lookup based on OpenStreetMap data
Stars: ✭ 159 (-14.97%)
Mutual labels:  routing
Ccna60d
60天通过思科认证的网络工程师考试
Stars: ✭ 155 (-17.11%)
Mutual labels:  routing
Vite Plugin Voie
File system based routing plugin for Vite
Stars: ✭ 172 (-8.02%)
Mutual labels:  routing
Surge Rules
🦄 🎃 👻 Surge 规则集(DOMAIN-SET 和 RULE-SET),兼容 Surge for iOS 和 Surge for Mac 客户端。
Stars: ✭ 151 (-19.25%)
Mutual labels:  routing
Routemagic
Utility Library to get the most out of ASP.NET Routing.
Stars: ✭ 180 (-3.74%)
Mutual labels:  routing
Routing
The routing core of itinero.
Stars: ✭ 145 (-22.46%)
Mutual labels:  routing
Router
⚡️ A lightning fast HTTP router
Stars: ✭ 158 (-15.51%)
Mutual labels:  routing
Riptide
Client-side response routing for Spring
Stars: ✭ 169 (-9.63%)
Mutual labels:  routing
Openrouteservice App
🚙 The open source route planner app with plenty of features.
Stars: ✭ 187 (+0%)
Mutual labels:  routing
Arouteserver
A tool to automatically build (and test) feature-rich configurations for BGP route servers.
Stars: ✭ 181 (-3.21%)
Mutual labels:  routing

Ataraxy

Build Status

A data-driven routing and destructuring library for Ring. This library is still being developed, so some functionality may change before we hit version 1.0.0.

Rationale

There are several data-driven routing libraries for Ring, such as bidi, Silk and gudu. Ataraxy differs from them because it not only seeks to match a route, it also destructures the incoming request.

In this sense it is similar to Compojure, in that the idea is to remove extraneous information. However, while Compojure is designed to use chains of functions, Ataraxy defines its functionality through a declarative data structure.

Example

{["/api" {uid :identity}]
 {"/products"
   {[:get]                [:products/list uid]
    [:get "/" pid]        [:products/get uid ^uuid pid]
    [:get "/search" #{q}] [:products/search uid q]
    [:post {body :body}]  [:products/new uid body]}}}

Installation

Add the following dependency to your project.clj file:

[ataraxy "0.4.2"]

Routing

Ataraxy uses a data structure to tell it how to route and destructure requests. See the following section on syntax for details.

(def routes '{"/foo" [:foo]})

We can match a request map to a result with matches:

(require '[ataraxy.core :as ataraxy])

(ataraxy/matches routes {:uri "/foo"})
=> [:foo]

If Ataraxy cannot correctly match any route, then an error result from the ataraxy.error namespace is returned. For example:

(ataraxy/matches routes {:uri "/bar"})
=> [:ataraxy.error/unmatched-path]

See the errors section for more details.

For performance, we can also pre-compile the routing data:

(def compiled-routes (ataraxy/compile routes))

The resulting object can be used in matches in the same way as the raw data structure:

(ataraxy/matches compiled-routes {:uri "/foo"})
=> [:foo]

Handlers

Once we have our routes, it's likely we want to turn them into a Ring handler function. Ataraxy has a function called handler for this purpose:

(defn foo [request]
  {:status 200, :headers {}, :body "Foo"})

(def handler
  (ataraxy/handler
   {:routes   routes
    :handlers {:foo foo}}))

This function takes a map with four keys:

  • :routes - the routes to match
  • :handlers - a map of result keys to Ring handlers
  • :middleware - a map of metadata keys to Ring middleware (optional)
  • :coercers - a map of symbols to coercer functions (optional)

The handler function is chosen by the key of the result. Two keys are added to the request map passed to the handler:

  • :ataraxy/result - contains the matched result
  • :route-params - a map of parameters matched in the path (included for compatibility)

The handler can also return a result vector instead of a request map. Each vector that is returned is checked against the handler map, until eventually a Ring response map is returned.

The ataraxy.response namespace defines a number of responses on the default handler, allowing for code like this:

(require '[ataraxy.response :as response])

(defn hello [{[_ name] :ataraxy/result}]
  [::response/ok (str "Hello " name)])

(def handler
  (ataraxy/handler
    {:routes   '{[:get "/hello/" name] [:hello name]}
     :handlers {:hello hello}}))

The default handler is set to ataraxy.handler/default, but can be changed by adding a handler to the :default key of the handler map.

Middleware is chosen based on the metadata that is applied to the result or to the containing routing table. For example:

(defn wrap-example [handler value]
  (fn [request]
    (let [response (handler request)]
      (assoc-in response [:header "X-Example"] value))))

(def handler
  (ataraxy/handler
   {:routes     {"/foo" ^:example [:foo]}
    :handlers   {:foo foo}
    :middleware {:example #(wrap-example % "test")}}))

This would add an X-Example header to the response of the handler. We can also pass an argument to the handler by setting the :example metadata key to something other than true:

(def handler
  (ataraxy/handler
   {:routes     {"/foo" ^{:example "test"} [:foo]}
    :handlers   {:foo foo}
    :middleware {:example wrap-example}}))

Custom coercers can be added to the handler by specifying the :coercers option. This is described in more detail in the coercers section.

Syntax

Ataraxy generates routes from a routing table, which is a Clojure map, or a list of alternating keys and values.

The keys of the table are routes, and the data type used defines a way of matching and destructuring a request.

The values are either results or nested tables.

Here's a semi-formal definition of the syntax:

table  = {<route result>+} | (<route result>+)
route  = keyword | string | symbol | set | map | [route+]
result = table | [keyword symbol*]

Results

Results are always vectors, beginning with a keyword, followed by zero or more symbols. For example:

[:foo id]

Results are paired with routes:

{["/foo/" id] [:foo id]}

The symbols in the route are passed into the result.

The symbols in the result may be tagged with a type they should be coerced into. For example:

[:foo ^int id]

See the coercers section for more detail.

Keyword routes

A keyword will match the request method. For example:

{:get [:foo]})

This route will match any request with the GET method.

String routes

A string will match the :path-info or :uri key on the request. For example:

{"/foo" [:foo]
 "/bar" [:bar]}

This example will match the URIs "/foo" and "/bar".

Symbol routes

Like strings, symbols match against the :path-info or :uri key on the request. Unlike strings, they match on a regular expression, and bind the string matched by the regular expression to the symbol.

By default the regex used is [^/]+. In other words, any character except a forward slash. The regex can be changed by adding a :re key to the symbol's metadata. For example:

{^{:re #"/d.g"} w [:word w]}

This will match URIs like "/dog", "/dig" and "/dug", and add the matched word to the result.

Set routes

A set of symbols will match URL-encoded parameters of the same name. For example:

{#{q} [:query q]}

This will match any request with q as a parameter. For example, "/search?q=foo".

In order to match query parameters, the request map needs a :query-params key, which is supplied by the wrap-params middleware in Ring.

By default, the parameters must be set for the route to match. If you want the parameters to be optional, you can prefix them with a "?".

{#{?q} [:query ?q]}

This works the same as the previous example, except that the route still matches if q is nil.

Map routes

A map will destructure the request. Any destructured symbol must not be nil for the route to match. For example:

{{{:keys [user]} :session} [:user user]}

This route will match any request map with a :user key in the session.

As with set routes, symbols prefixed with a "?" are considered optional and may be nil.

Vector routes

A vector combines the behavior of multiple routing rules. For example:

{[:get "/foo"] [:foo]}

This will match both the request method and the URI.

Strings and symbols will be combined in order, to allow complex paths to be matched. For example:

{[:get "/user/" name "/info"] [:get-user-info name]}

This will match URIs like "/user/alice/info" and pass the name "alice" to the result.

Nested tables

Nesting routing tables is an alternative way of combining routes. Instead of a result vector, a map or list may be specified. For example:

{"/foo"
 {"/bar" [:foobar]
  "/baz" [:foobaz]}})

This will match the URIs "/foo/bar" and "/foo/baz".

You can also use nesting and vectors together:

{["/user/" name]
 {:get [:get-user name]
  :put [:put-user name]}}

Errors

When something goes wrong, Ataraxy returns one of the following error results:

  • :ataraxy.error/unmatched-path
  • :ataraxy.error/unmatched-method
  • :ataraxy.error/missing-params
  • :ataraxy.error/missing-destruct
  • :ataraxy.error/failed-coercions
  • :ataraxy.error/failed-spec

If you're using the ataraxy.core/handler function, these are automatically converted into appropriate Ring response maps. However, it's generally worth customizing the error responses to the needs of your application.

Coercers

Coercers are functions that turn a string into a custom type. Any symbol in the result can be tagged with a symbol associated with a coercer function.

For example, it's common to want to change a parameter from a string into an int:

{[:get "/foo/" id] [:foo ^int id]}

The int and uuid coercers are included by default. We can easily add our own, however:

(defn ->float [s]
  (try (Double/parseDouble s) (catch NumberFormatException _)))

(def compiled-routes
  (ataraxy/compile
   '{[:get "/foo/" id] [:foo ^float id]}
   {'float ->float}))

And similarly to handlers:

(def handler
  (ataraxy/handler
   {:routes   '{[:get "/foo/" id] [:foo ^float id]}
    :coercers {'float ->float}}))

Specs

Results are validated via the :ataraxy/result spec. This is a multi-spec that dispatches off the key, and can be assigned behavior through the result-spec multimethod.

For example:

(require '[clojure.spec.alpha :as s])

(defmethod ataraxy/result-spec ::foo [_]
  (s/cat :key any? :id nat-int?))

This ensures that any result with ::foo as the key must have exactly two elements, with the second being a natural number.

If a spec fails, then a :ataraxy.error/failed-spec result is returned, which if left alone resolves to a 400 "Bad Request" response in the handler.

License

Copyright © 2017 James Reeves

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].