• Stars
    star
    412
  • Rank 105,024 (Top 3 %)
  • Language
    Clojure
  • License
    Eclipse Public Li...
  • Created almost 9 years ago
  • Updated about 1 year ago

Reviews

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

Repository Details

Monadic error utilities for general use in Clojure(script) projects

Failjure

Run Tests Clojars Project bb compatible

Failjure is a utility library for working with failed computations in Clojure(Script). It provides an alternative to exception-based error handling for applications where functional purity is more important.

It was inspired by Andrew Brehaut's error monad implementation.

Installation

Add the following to your build dependencies:

Clojars Project

You can also include the specs via the failjure-spec project, if you're into that sort of thing:

Clojars Project

Example

(require '[failjure.core :as f])

;; Write functions that return failures
(defn validate-email [email]
    (if (re-matches #".+@.+\..+" email)
      email
      (f/fail "Please enter a valid email address (got %s)" email)))

(defn validate-not-empty [s]
  (if (empty? s)
    (f/fail "Please enter a value")
    s))

;; Use attempt-all to handle failures
(defn validate-data [data]
  (f/attempt-all [email (validate-email (:email data))
                  username (validate-not-empty (:username data))
                  id (f/try* (Integer/parseInt (:id data)))]
    {:email email
     :username username}
    (f/when-failed [e]
      (log-error (f/message e))
      (handle-error e))))

Quick Reference

HasFailed

The cornerstone of this library, HasFailed is the protocol that describes a failed result. Failjure implements HasFailed for Object (the catch-all not-failed implementation), Exception, and the built-in Failure record type, but you can add your own very easily:

(defrecord AnnotatedFailure [message data]
  f/HasFailed
  (failed? [self] true)
  (message [self] (:message self)))

fail

fail is the basis of this library. It accepts an error message with optional formatting arguments (formatted with Clojure's format function) and creates a Failure object.

(f/fail "Message here") ; => #Failure{:message "Message here"}
(f/fail "Hello, %s" "Failjure") ; => #Failure{:message "Hello, Failjure"}

failed? and message

These two functions are part of the HasFailed protocol underpinning failjure. failed? will tell you if a value is a failure (that is, a Failure, a java Exception or a JavaScript Error.

attempt

Added in 2.1

Accepts a value and a function. If the value is a failure, it is passed to the function and the result is returned. Otherwise, value is returned.

(defn handle-error [e] (str "Error: " (f/message e)))
(f/attempt handle-error "Ok")  ;=> "Ok"
(f/attempt handle-error (f/fail "failure"))  ;=> "Error: failure"

Try it with partial!

attempt-all

attempt-all wraps an error monad for easy use with failure-returning functions. You can add any number of bindings and it will short-circuit on the first error, returning the failure.

(f/attempt-all [x "Ok"] x)  ; => "Ok"
(f/attempt-all [x "Ok"
              y (fail "Fail")] x) ; => #Failure{:message "Fail"}

You can use when-failed to provide a function that will handle an error:

(f/attempt-all [x "Ok"
                y (fail "Fail")]
  x
  (f/when-failed [e]
    (f/message e))) ; => "Fail"

ok-> and ok->>

If you're on-the-ball enough that you can represent your problem as a series of compositions, you can use these threading macros instead. Each form is applied to the output of the previous as in -> and ->> (or, more accurately, some-> and some->>), except that a failure value is short-circuited and returned immediately.

Previous versions of failjure used attempt-> and attempt->>, which do not short-circuit if the starting value is a failure. ok-> and ok->> correct this shortcoming

(defn validate-non-blank [data field]
  (if (empty? (get data field))
    (f/fail "Value required for %s" field)
    data))

(let [result (f/ok->
              data
              (validate-non-blank :username)
              (validate-non-blank :password)
              (save-data))]
  (when (f/failed? result)
    (log (f/message result))
    (handle-failure result)))

as-ok->

Added in 2.1

Like clojure's built-in as->, but short-circuits on failures.

(f/as-ok-> "k" $
  (str $ "!")
  (str "O" $))) ; => Ok!

(f/as-ok-> "k" $
  (str $ "!")
  (f/try* (Integer/parseInt $))
  (str "O" $))) ; => Returns (does not throw) a NumberFormatException

try*

This library does not handle exceptions by default. However, you can wrap any form or forms in the try* macro, which is shorthand for:

(try
  (do whatever)
  (catch Exception e e))

Since failjure treats returned exceptions as failures, this can be used to adapt exception-throwing functions to failjure-style workflows.

try-all

A version of attempt-all which automatically wraps each right side of its bindings in a try* is available as try-all (thanks @lispyclouds):

(try-all [x (/ 1 0)
          y (* 2 3)]
  y)  ; => java.lang.ArithmeticException (returned, not thrown)

(if-|when-)-let-(ok?|failed?)

Failjure provides the helpers if-let-ok?, if-let-failed?, when-let-ok? and when-let-failed? to help with branching. Each has the same basic structure:

(f/if-let-failed? [x (something-which-may-fail)]
  (handle-failure x)
  (handle-success x))
  • If no else is provided, the if- variants will return the value of x
  • The when- variants will always return the value of x

assert-with

The assert-with helper is a very basic way of adapting non-failjure-aware functions/values to a failure context. The source is simply:

(defn assert-with
  "If (pred v) is true, return v
   otherwise, return (f/fail msg)"
  [pred v msg]
  (if (pred v) v (fail msg)))

The usage looks like this:

(f/attempt-all
  [x (f/assert-with some? (some-fn) "some-fn failed!")
   y (f/assert-with integer? (some-integer-returning-fn) "Not an integer.")]
  (handle-success x)
  (f/when-failed [e] (handle-failure e)))

The pre-packaged helpers assert-some?, assert-nil?, assert-not-nil?, assert-not-empty?, and assert-number? are provided, but if you like, adding your own is as easy as (def assert-my-pred? (partial f/assert-with my-pred?)).

Changelog

2.3.0

Added clj-kondo support and indent annotations.

2.2.0

(Re-)added AOT compilation to the new leiningen project. This may help resolve errors with some project configurations.

2.1.1

Fix a deployment whoopsie causing attempt to have reversed argument order from what is documented here. It was fine in my REPL, I swear!

2.1.0

USE 2.1.1 INSTEAD

Added attempt and as-ok->. Changed from boot to leiningen for builds.

2.0.0

Added ClojureScript support. Since the jar now includes .cljc instead of .clj files, which could break older builds, I've decided this should be a major version. It should in general be totally backwards-compatible though.

Notable changes:

  • ClojureScript support (thanks @snorremd)
  • *try now wraps its inputs in a function and returns (try-fn *wrapped-fn*). This was necessary to keep the clj and cljs APIs consistent, but could break some existing use cases (probably).

1.5.0

Added try-all feature

1.4.0

Resolved issues caused by attempting to destructure failed results.

1.3.0

Fix bug where ok->/> would sometimes double-eval initial argument.

1.2.0

Refactored attempt-all, attempt->, and attempt->> to remove dependency on monads

1.1.0

Added assert helpers

1.0.1

This version is fully backwards-compatible with 0.1.4, but failjure has been in use long enough to be considered stable. Also I added a .1 because nobody trusts v1.0.0.

  • Added ok?, ok->, ok->>, if-let-ok?, when-let-ok?, if-let-failed? and when-let-failed?

0.1.4

  • Added changelog.

License

Copyright 2016 Adam Bard and Andrew Brehaut

Distributed under the Eclipse Public License v1.0 (same as Clojure).

More Repositories

1

learnxinyminutes-docs

Code documentation written as code! How novel and totally my idea!
Markdown
11,476
star
2

angular-elasticsearch-demo

JavaScript
63
star
3

learnxinyminutes-site

Ruby
44
star
4

functools-for-matlab

Tested, working functional programming tools for MATLAB. Yes, really.
Objective-C
40
star
5

googlegantt.py

Create Gantt charts using Google Charts' API!
Python
28
star
6

lein-kotlinc

Compile kotlin files from Leiningen
Clojure
18
star
7

flask-skeleton

A Flask Skeleton project with Sass and Coffeescropt
Python
13
star
8

so-many-wordcounts

Clojure
11
star
9

fungibility

A Readability clone for use with heroku
JavaScript
9
star
10

Circulure

Ripping off circular.io with a Clojure backend
Clojure
9
star
11

buddy-test

An example implemention of auth using Buddy
Clojure
9
star
12

steam-liquidator

Sell all your Steam items for cents upon cents!
Python
9
star
13

schengencalc

A handy-dandy Schengen Zone visa calculator
JavaScript
9
star
14

sparkquet

Demo using Spark with Parquet and Protobufs
Clojure
9
star
15

Freelance-Website

The homepage I use for my freelance work
Python
8
star
16

Nurblizer

Ruby
7
star
17

fregeweb

Just some tests integrating Frege and Clojure (with Ring)
Clojure
6
star
18

bandito

Simple multi-armed bandit testing in Clojure for Ring web applications.
Clojure
6
star
19

minecraft-enchant

Minecraft Enchanting Calculator
Python
6
star
20

nurblizer-clj

The nurblizer, Clojure style
Clojure
5
star
21

elasticsearch-rosettastone

A few implementations of basic elasticsearch algorithms.
Python
5
star
22

worldclassifiedlist

The project backing worldclassifiedlist.com
CSS
4
star
23

example.clj

A barebones example Clojure web project using Ring, Compojure and Clostache
Clojure
4
star
24

diamond_dash_bot

Diamond Dash Bot
Python
4
star
25

py-nurblizer

The nurblizer app from before, but written in python.
Python
4
star
26

coffeedate

Easy date parsing and formatting for JS/coffeescript.
JavaScript
3
star
27

refactoring-sample

Refactoring-Sample
Java
3
star
28

schemongo

The mongo schema library for clojure with the embarrassing name
Clojure
3
star
29

franslations

Franslate.me translation files
3
star
30

failjure-spec

Specs for failjure
Clojure
3
star
31

silly-mud-game

Clojure
2
star
32

nurblizer-php

The nurblizer, approximated in PHP
PHP
2
star
33

guess-the-subreddit

A subreddit guessing game made with Typescript and React
TypeScript
2
star
34

deckoptimizer

Just dork stuff
Clojure
1
star
35

vimrc

My .vimrc file.
Vim Script
1
star
36

jquery.multiselect.js

A simple jquery plugin to transform <select multiple="multiple"> into a more useable version.
JavaScript
1
star
37

wagr

Wagr: An Example Clojure Web App
Clojure
1
star