Git Product home page Git Product logo

failjure's People

Contributors

adambard avatar ashwinbhaskar avatar lispyclouds avatar pratik97 avatar vendethiel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

failjure's Issues

Threading macro for handling of nil and exceptions along with explicit failures

Using the f/ok->> macro allows short circuiting only on explicit failures, but since the regular Clojure idiom is to use nil punning, it would be useful to have a threading macro that provides the benefit of some->> as well. The modified macro below allows me to unify nils, exceptions as well explicit fails into the failjure interface. If the code below looks ok, could you please include it as part of failjure?

(defmacro check_attempt->>
  ([start] start)
  ([start form] `(domonad f/error-m [x# (->> ~start ~form)] x#))
  ([start form & forms]
   `(let [new-start# (check_attempt->> ~start ~form)]
      (cond
        (f/failed? new-start#) new-start#
        (nil? new-start#) (f/fail (str "got nil at " ~form))
        :else (check_attempt->> new-start# ~@forms)))))


(defmacro check->>
  ([start & forms]
   `(if (f/failed? ~start)
      ~start
      (f/try*
       (check_attempt->> ~start ~@forms)))))

Suggestion: Rename `if-failed` to `when-failed`

First of all, this is awesome! Thanks @adambard โœจ ๐Ÿ‘

As I read your introductory blog post, I had an initial reaction when I saw if-failed:

(f/attempt-all [,,,]
  ,,,
  (f/if-failed [e]
    (log-error (f/message e))
    (handle-error e)))

I saw two forms after if and my first intuition was that they would be "then" and "else" cases. This obviously doesn't make sense here, so this didn't hang me up for long.

...But, maybe we could avoid even the chance of momentary confusion by renaming to when-failed.

Oposite of try-all: try-all-failed and similar

I think try-all-failed and similar might also be useful.

I am trying to parse some input from user (a time Duration) and trying to be forgiving.
In my case I need to try out all options and stop when I get a non-failure.

So my algorithm is :

  • try to parse user input
  • if failed try to parse user input with "PT" pre-pended
  • if failed try to parse user input with "P" pre-pended
  • give up with parsing
  • or if one did not fail, return that value

I think code would look like this:
(beginner with Failjure, code might not be correct)

(try-all-failed [duration (try-parse s)
                         duration (try-parse (str "PT" s))
                         duration (try-parse (str "P" s))]
duration
(f/fail "Unable to parse duration"))

Some context
https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#parse-java.lang.CharSequence-

 Examples:

    "PT20.345S" -- parses as "20.345 seconds"
    "PT15M"     -- parses as "15 minutes" (where a minute is 60 seconds)
    "PT10H"     -- parses as "10 hours" (where an hour is 3600 seconds)
    "P2D"       -- parses as "2 days" (where a day is 24 hours or 86400 seconds)
    "P2DT3H4M"  -- parses as "2 days, 3 hours and 4 minutes"
    "P-6H3M"    -- parses as "-6 hours and +3 minutes"
    "-P6H3M"    -- parses as "-6 hours and -3 minutes"
    "-P-6H+3M"  -- parses as "+6 hours and -3 minutes"

Plans for clojurescript support?

@adambard Have you tried porting Failjure to clojurescript? Failjure uses algo.monads, which just seems to use one function name-with-attributes, from clojure.tools.macro. Ie, it doesn't support clojurescript. But Cats does.

Anyways, I thought it would be really nice to reuse tha same Either / Error monadic semantics, on both layers of our application.

Thoughts?

Introduce as-ok->

Introduce the failjure version of as-> which short circuits if any of the forms returns a failjure

Document the using with with-open

Hello!

First of all, thanks for failjure

I would be glad if you write an example how to use failjure for cases, where a user needs to close opened resources in any case. E.g. Iโ€™m doing the next:

(f/attempt-all [reader (pdf-reader source)
                document (pdf-document reader)
                fields (fields document)]
  (do
    (.close document)
    (.close reader)
    fields)
  (f/when-fail [e]
    ; Here I do not have an access to reader and document
    ; to close them
  ))

I wonder if there any mechanism to close the document and the reader in any case, like with with-open

Compile blows up on 1.9.0-alpha when failjure is 'required'

Hi, On 1.9-alpha16, just including the failjure declaration in a namespaces causes the following error

Exception in thread "main" java.lang.ExceptionInInitializerError
	at clojure.main.<clinit>(main.java:20)
Caused by: clojure.lang.ExceptionInfo: Call to clojure.core/defn did not conform to spec:
In: [0] val: clojure.algo.monads/m+m-join+m fails spec: :clojure.core.specs/defn-args at: [:args :name] predicate: simple-symbol?
:clojure.spec/args  (clojure.algo.monads/m+m-join+m [m-bind m-result m-zero m-plus m] (clojure.tools.macro/with-symbol-macros (m-bind m identity)))
 {:clojure.spec/problems [{:path [:args :name], :pred simple-symbol?, :val clojure.algo.monads/m+m-join+m, :via [:clojure.core.specs/defn-args :clojure.core.specs/defn-args], :in [0]}], :clojure.spec/args (clojure.algo.monads/m+m-join+m [m-bind m-result m-zero m-plus m] (clojure.tools.macro/with-symbol-macros (m-bind m identity)))}, compiling:(clojure/algo/monads.clj:248:1)
	at clojure.lang.Compiler.load(Compiler.java:7442)
	at clojure.lang.RT.loadResourceScript(RT.java:374)
	at clojure.lang.RT.loadResourceScript(RT.java:365)
	at clojure.lang.RT.load(RT.java:455)
	at clojure.lang.RT.load(RT.java:421)
	at clojure.core$load$fn__7831.invoke(core.clj:6008)
	at clojure.core$load.invokeStatic(core.clj:6007)
	at clojure.core$load.doInvoke(core.clj:5991)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5812)
	at clojure.core$load_one.invoke(core.clj:5807)
	at clojure.core$load_lib$fn__7776.invoke(core.clj:5852)
	at clojure.core$load_lib.invokeStatic(core.clj:5851)
	at clojure.core$load_lib.doInvoke(core.clj:5832)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5889)
	at clojure.core$load_libs.doInvoke(core.clj:5873)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5911)
	at clojure.core$require.doInvoke(core.clj:5911)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at failjure.core$eval16561$loading__7717__auto____16562.invoke(core.clj:1)
	at failjure.core$eval16561.invokeStatic(core.clj:1)
	at failjure.core$eval16561.invoke(core.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:6978)
	at clojure.lang.Compiler.eval(Compiler.java:6967)
	at clojure.lang.Compiler.load(Compiler.java:7430)
	at clojure.lang.RT.loadResourceScript(RT.java:374)
	at clojure.lang.RT.loadResourceScript(RT.java:365)
	at clojure.lang.RT.load(RT.java:455)
	at clojure.lang.RT.load(RT.java:421)
	at clojure.core$load$fn__7831.invoke(core.clj:6008)
	at clojure.core$load.invokeStatic(core.clj:6007)
	at clojure.core$load.doInvoke(core.clj:5991)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5812)
	at clojure.core$load_one.invoke(core.clj:5807)
	at clojure.core$load_lib$fn__7776.invoke(core.clj:5852)
	at clojure.core$load_lib.invokeStatic(core.clj:5851)
	at clojure.core$load_lib.doInvoke(core.clj:5832)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5889)
	at clojure.core$load_libs.doInvoke(core.clj:5873)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5911)
	at clojure.core$require.doInvoke(core.clj:5911)
	at clojure.lang.RestFn.invoke(RestFn.java:436)
	at gs_authorization.routes.services.actions$eval16555$loading__7717__auto____16556.invoke(actions.clj:1)

Monads dep was removed

Hello @adambard !
You removed monads dependency from failjure lib.
I am using previous failjure version in my project and i am using it as a monad in several places.
For example, m-map over collection where projection function can return error.

Can you enlighten me why did you removed monad dependency?
Is it Occam Razor application?

Thanks!

clj-kondo config broken for `try*` (triggers: `missing-clause-in-try`)

The current clj-kondo config shipped with the project maps failjure.core/try* to clojure.core/try. This causes clj-kondo to complain about the the expression Missing catch or finally in try.

current workaround is to set .clj-kondo/config.edn to be:

{:lint-as {failjure.core/try* clj-kondo.lint-as/def-catch-all}}

Feature request: `when-ok` macro

For the occasions when you only care that the form passes and do not need a value from it, I find myself using when-let-ok?:

(f/when-let-ok? [_ (some-fn-that-returns-a-failure-or-a-value...)]
  ::woohoo)

I propose to add the macro when-ok:

(defmacro when-ok [form & body]
  (when-let-ok? [_# ~form]
    ~@body))

So we can write:

(f/when-ok (some-fn-that-returns-a-failure-or-a-value...)
  ::woohoo)

Happy to do a PR for this with tests if you're interested in adding it.

ps. Great library by the way, thank you for your efforts!

Destructuring inside attempt-all causes an exception/failjure that would normally be short-circuited not to be

De-structuring inside attempt-all causes a returned java Exception or failjure Failure that would normally be short-circuited to not be short-circuited.

(f/attempt-all [m (f :a)
                {:keys [a b c]} m]
          3)

The above code gets around the problem. The problem would be evident if you avoided having m, so directly de-structured. Obviously f is returning something that f/attempt-all ought to be catching (a java Exception or failjure Failure).

Proposal: failure handling without binding

I don't see any way to handle failure without binding it to a symbol. attempt-all is great but if we have only one binding then there's a lot of boilerplate and nesting. My proposal is to add attempt macro which will let us handle failure without having to bind it manually.

Example with attempt-all:

(f/attempt-all [result (f/fail "Failed")]
  result
  (f/when-failed [e] (handle-error e)))

Boilerplate. We don't do anything with the result if there's no error.


Example with when-let-failed?:

(f/when-let-failed? [result (f/fail "Failed")]
  (handle-error result))

Less boilerplate, but we have to customize auto-indent and our code is less linear + we still have to name result

"There are only two hard things in Computer Science: cache invalidation and naming things."
Phil Karlton


Example with attempt:

(f/attempt (f/fail "Failed") handle-error)

0 boilerplate, elegant one-liner.


Example of real world use-case with when-let-failed?:

(defn create-todo [input]
  (f/when-let-failed? [result (f/ok->input
                               (validate)
                               (prepare)
                               (insert-into-db!)
                               (result-json))]
    (create-todo-error result)))

Example of real world use-case with attempt:

(defn create-todo [input]
  (f/attempt
    (f/ok-> input
            (validate)
            (prepare)
            (insert-into-db!)
            (result-json))
    create-todo-error)))

Ideal name imo would be when-failed but there's already macro with this name so attempt seams reasonable. It's almost like attempt-all but you attempt to get only one value and if there's only one value there's no point to bind it to anything

Feature Request: `f/attempt` with implicit `f/try*`

Think it would be handy to have a macro like f/attempt but wraps val-or-failed in an implicit f/try*.

Maybe something like f/try? or change f/attempt?:

(f/try
 #(f/fail "I failed!: %s" (f/message %))
 (throw (ex-info "I throw" {})))

ok? for multiple results

I was wondering if makes sense for change the ok? fn to also validate a list of results?

(defn ok?
  [v]
  (if (coll? v)
    (every? ok? v)
    (not (failed? v))))

I think would be nice to add a feature like this or on the other hand if you want to have a different function with it's own semantic we can just add a different fn.

your thoughts on this one @adambard

Missing the stack traces on errors

Hi,

I'm using this library in a project and I kind of like the flow.
However I'm missing the stack traces when an a failure is reached.

Would love to have a way to capture the stack-trace on fail so to have an easier experience when debugging.
Is this feasible?

Thanks,
Euge

Please aot compile core.cljs

Problem

We have the following issue/exception when we're importing failjure during dev-time (repl-based workflow):

java.lang.NoClassDefFoundError: failjure/core/HasFailed
	at bulk_gateway.rest.accounts$get_acccount_balance.invokeStatic(accounts.clj:68)
	at bulk_gateway.rest.accounts$get_acccount_balance.invoke(accounts.clj:52)
...

Background

  • Our project setups are "interesting", but basically what we do is to load our own namespaces using dynamic (require ...) calls after finding a list of namespaces in our integrant configuration file. These namespaces then have (ns bulk-gateway.rest.accounts (require [failjure.core]... dependencies on failjure.
  • One work-around we have is after the repl starts, we can (load-file "src/bulk_gateway/rest/accounts.clj") after which the NoClassDefFoundError exceptions go away.
  • We do NOT have this problem in our deployed artifacts (created using lein uberjar).

We've had similar issues with our own shared libraries that have (defprotocol ... in them and solved it by adding an :out [...] in the project.clj of the client lib project.

Solution

I cloned your repo locally and added an :aot [failjure.core] to failjure's project.clj and our issue goes away after lein install.

As far as I know this should be no-impact change for clients.

(defproject failjure "2.1.1"
  :description "Simple helpers for treating failures as values"
  :url         "https://github.com/adambard/failjure"
  :license     {:name "Eclipse Public License"
                :url "http://www.eclipse.org/legal/epl-v10.html"}

  :dependencies []

  :repl-options {:init-ns failjure.core}

  :plugins [[lein-cljsbuild "1.1.8"]
            [lein-doo "0.1.10"]]

  :aot [failjure.core]

  :profiles
  {:provided {:dependencies [[org.clojure/clojure       "1.10.1"]
                             [org.clojure/clojurescript "1.10.764"]]}}
  :cljsbuild
  {:builds [{:id "test"
             :source-paths ["src" "test"]
             :compiler {:output-to "target/testable.js"
                        :main failjure.runner
                        :target :nodejs
                        :optimizations :none}}]})

Maybe you should get rid of the when-failed macro...

I think it would be much better, if attempt-all would accept a plain function as a failure-handler instead of a when-failed macro - remember the first rule of the macro-club ;-)

And it just takes a slight modification:

(defmacro attempt-all
  "Used like `let`, but short-circuits in case of
  a failed binding. If a `failure-handler` fn is provided, it is called to handle the failure.
  Unlike `let`, only accepts a single form to execute after the bindings.
    (attempt-all [x \"Ok\"
                  y (fail \"Fail\")]
      x
      (fn handle-failure [e]
        (message e))) ; => \"Fail\"
  "
  ([bindings return]
   `(domonad error-m ~bindings ~return))
  ([bindings return failure-handler]
   `(let [result# (attempt-all ~bindings ~return)]
      (if (failed? result#)
        (~failure-handler result#)
        result#))))

What do you think?

Regards, Jan

Failure spec checking fails for functions returning collections

examples use defn-spec from orchestra
This works:

(defn-spec test-failure-works (s/or :ok string? :fail ::fs/failure)
  []
  (f/fail "something wrong"))

This does not work --looks like the invocation of 'empty' on Failure record from the clojure.spec code is the problem:

(defn-spec test-failure-doesnt-work (s/or :ok (s/coll-of string?) :fail ::fs/failure)
  []
  (f/fail "something wrong"))

Unhandled java.lang.UnsupportedOperationException
Can't create empty: failjure.core.Failure

            core.clj:   36  failjure.core.Failure/empty
            core.clj: 5187  clojure.core/empty
            core.clj: 5181  clojure.core/empty
           alpha.clj: 1229  clojure.spec.alpha/every-impl/cfns/fn
           alpha.clj: 1243  clojure.spec.alpha/every-impl/reify
           alpha.clj: 1017  clojure.spec.alpha/or-spec-impl/fn
           alpha.clj: 1052  clojure.spec.alpha/or-spec-impl/reify
           alpha.clj:  150  clojure.spec.alpha/conform
           alpha.clj:  146  clojure.spec.alpha/conform
            test.clj:   97  orchestra.spec.test/spec-checking-fn/conform!
            test.clj:  125  orchestra.spec.test/spec-checking-fn/fn
         RestFn.java:  397  clojure.lang.RestFn/invoke

Allow arbitrary data to be associated with failure?

My code often fails in varying and spectacular ways :)

What would you think about allowing arbitrary maps to be associated with failures as well as strings?

My database might fail and my Validateur validations might fail, and I need to be able to render those in different ways.

If I could add a map to a failure then I could put the error map from Validateur into my failure and render that on my front end.

What do you think? Looking at your library and learning about monads has really changed the way I think about error handling, thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.