• Stars
    star
    209
  • Rank 188,325 (Top 4 %)
  • Language
    Clojure
  • License
    Eclipse Public Li...
  • Created almost 9 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A data-driven Ring routing and destructuring library

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.3"]

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 © 2022 James Reeves

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

More Repositories

1

compojure

A concise routing library for Ring/Clojure
Clojure
4,029
star
2

hiccup

Fast library for rendering HTML in Clojure
Clojure
2,571
star
3

integrant

Micro-framework for data-driven architecture
Clojure
1,232
star
4

cljfmt

A tool for formatting Clojure code
Clojure
1,112
star
5

environ

Library for managing environment variables in Clojure
Clojure
923
star
6

medley

A lightweight library of useful Clojure functions
Clojure
867
star
7

codox

Clojure documentation tool
Clojure
667
star
8

ragtime

Database-independent migration library
Clojure
608
star
9

lein-ring

Ring plugin for Leiningen
Clojure
501
star
10

hashp

A better "prn" for debugging
Clojure
442
star
11

eftest

Fast and pretty Clojure test runner
Clojure
424
star
12

reagi

An FRP library for Clojure and ClojureScript
Clojure
232
star
13

clout

HTTP route-matching library for Clojure
Clojure
230
star
14

crypto-password

Library for securely hashing passwords
Clojure
204
star
15

clj-aws-s3

S3 client library for Clojure
Clojure
198
star
16

clojure-toolbox.com

Source to clojure-toolbox.com
CSS
179
star
17

reloaded.repl

REPL functions to support the reloaded workflow
Clojure
178
star
18

clucy

Clojure interface to Lucene
Clojure
172
star
19

haslett

A lightweight WebSocket library for ClojureScript
Clojure
172
star
20

integrant-repl

Reloaded workflow functions for Integrant
Clojure
158
star
21

lein-beanstalk

Leiningen plugin for Amazon's Elastic Beanstalk service
Clojure
149
star
22

ring-oauth2

OAuth 2.0 client middleware for Ring
Clojure
144
star
23

brutha

Simple ClojureScript interface to React
Clojure
139
star
24

progrock

A functional Clojure progress bar for the command line
Clojure
134
star
25

lein-auto

A Leiningen plugin that executes tasks when files are modifed
Clojure
132
star
26

ns-tracker

Library to keep track of changes to Clojure source files
Clojure
114
star
27

meta-merge

A standalone implementation of Leiningen's meta-merge function
Clojure
105
star
28

ring-mock

Library to create mock ring requests for unit tests
Clojure
86
star
29

ring-anti-forgery

Ring middleware to prevent CSRF attacks
Clojure
76
star
30

crypto-random

Clojure library for generating cryptographically secure random bytes and strings
Clojure
72
star
31

crouton

HTML parsing library for Clojure
Clojure
68
star
32

comb

Clojure templating library
Clojure
67
star
33

ittyon

Library to manage distributed state for games
Clojure
58
star
34

compojure-example

An example Compojure project
Clojure
57
star
35

hiccup-bootstrap

Twitter's bootstrap in Hiccup
Clojure
57
star
36

lein-generate

Leiningen plugin for generating source file templates
Clojure
54
star
37

euclidean

Fast, immutable math for 3D geometries in Clojure
Clojure
52
star
38

impi

ClojureScript library for using Pixi.js through immutable data
Clojure
51
star
39

ring-server

Clojure
51
star
40

valip

Validations library for Clojure 1.2
Clojure
51
star
41

rotary

DynamoDB API for Clojure
Clojure
47
star
42

flupot

ClojureScript functions for creating React elements
Clojure
45
star
43

re-rand

Clojure library to generate random strings from regular expressions
Clojure
43
star
44

ring-webjars

Ring middleware to serve assets from WebJars
Clojure
36
star
45

ring-refresh

A Clojure middleware library for Ring that automatically triggers a browser refresh
Clojure
33
star
46

abrade

Clojure library for web scraping
Clojure
32
star
47

ring-jetty-component

A component for the standard Ring Jetty adapter
Clojure
32
star
48

tcp-server

Clojure TCP server library
Clojure
32
star
49

intentions

Multimethods that combine rather than override inherited behavior
Clojure
31
star
50

compojure-template

Compojure project template for Leiningen
Clojure
28
star
51

suspendable

A Clojure library to add suspend and resume methods to Component
Clojure
27
star
52

ring-serve

Ring development web server
Clojure
25
star
53

decorate

Clojure macros for decorating functions
Clojure
24
star
54

crypto-equality

A small Clojure library for securely comparing strings or byte arrays
Clojure
24
star
55

resauce

Clojure library for handling JVM resources
Clojure
24
star
56

fact

Unit testing library for Clojure (no longer in active dev)
Clojure
23
star
57

dotfiles

My configuration files
Emacs Lisp
21
star
58

inquest

A library for non-invasive monitoring in Clojure
Clojure
20
star
59

evaljs

Evaluate Javascript code and libraries in Clojure
Clojure
20
star
60

fish-git

Git completions and functions for the Fish Shell
18
star
61

snowball-stemmer

Snowball Stemmer for Clojure
Java
17
star
62

hop

An experimental declarative build tool for Clojure
Clojure
16
star
63

build

Clojure
15
star
64

coercer

Library to convert Clojure data into different types
Clojure
14
star
65

whorl

Generate unique fingerprints for Clojure data structures
Clojure
14
star
66

clojure-over-ajax

Ajax Clojure REPL based on why's Try Ruby
JavaScript
13
star
67

flupot-pixi

A ClojureScript wrapper around react-pixi
Clojure
13
star
68

websocket-example

Small example Ring/Aleph project for demonstrating websockets
Clojure
12
star
69

ring-json-response

Ring responses in JSON
Clojure
11
star
70

crumpets

Clojure library for dealing with color
Clojure
10
star
71

duct-hikaricp-component

Clojure component for managing a HikariCP connection pool
Clojure
10
star
72

clojure-dbm

Clojure interface to key-value databases
Clojure
9
star
73

hanami

A Clojure utility library for Heroku web applications
Clojure
9
star
74

strowger

A ClojureScript library for managing DOM events
Clojure
9
star
75

crypto-keystore

Clojure library for dealing with Java keystores
Clojure
8
star
76

substream

Stream subclassing in Clojure
Clojure
7
star
77

clj-daemon

Clojure daemon to avoid JVM startup time
Clojure
7
star
78

ring-reload-modified

Ring middleware that automatically reloads modifed source files
Clojure
7
star
79

duct-ragtime-component

Clojure component for managing migrations with Ragtime
Clojure
5
star
80

ring-honeybadger

Ring middleware for sending errors to HoneyBadger
Clojure
4
star
81

imprimatur

Data visualization library for ClojureScript and React
Clojure
4
star
82

hassium

Another Clojure MongoDB library
Clojure
4
star
83

contributing

Contributor's Guide
4
star
84

lein-template

Clojure
4
star
85

delegance

A Clojure library for remote evaluation
Clojure
3
star
86

po

A command-line tool for organizing project-specific scripts
Go
3
star
87

lein-version-script

A Leiningen plugin to set the project version from a shell script
Clojure
3
star
88

capra

An extensible package manager for Clojure
Clojure
3
star
89

eclair

Clojure
3
star
90

wrepl

Web-based Clojure REPL
Clojure
2
star
91

pocketses

Personal Wiki template that uses Gollum
CSS
2
star
92

clj-less

LESS interpreter for Clojure (http://lesscss.org)
Clojure
2
star
93

dewdrop

Web UI framework
2
star
94

ubitcoin

Bitcoin GUI for Ubuntu
Python
2
star
95

clojure-sandbox

Miscellaneous Clojure libraries that needed a home
Clojure
2
star
96

capra-server

RESTful package server
Clojure
1
star
97

delegance-aws

Library to integrate Delegance with Amazon Web Services
Clojure
1
star
98

weavejester.github.com

JavaScript
1
star
99

dojo-poetry

Code for Clojure Dojo 2012-08-28
Clojure
1
star
100

databstract

1
star