{"id":20092691,"url":"https://github.com/kanaka-io/play-monadic-actions","last_synced_at":"2025-08-20T20:32:06.867Z","repository":{"id":24563321,"uuid":"27970645","full_name":"Kanaka-io/play-monadic-actions","owner":"Kanaka-io","description":"A simple scala DSL to allow clean and monadic style for Play! Actions","archived":false,"fork":false,"pushed_at":"2020-12-30T23:29:54.000Z","size":2467,"stargazers_count":128,"open_issues_count":8,"forks_count":27,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-07-29T05:17:43.725Z","etag":null,"topics":["dsl","monad","playframework","scala"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Kanaka-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-12-13T19:05:20.000Z","updated_at":"2024-02-19T15:51:54.000Z","dependencies_parsed_at":"2022-08-23T04:11:12.524Z","dependency_job_id":null,"html_url":"https://github.com/Kanaka-io/play-monadic-actions","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/Kanaka-io/play-monadic-actions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kanaka-io%2Fplay-monadic-actions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kanaka-io%2Fplay-monadic-actions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kanaka-io%2Fplay-monadic-actions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kanaka-io%2Fplay-monadic-actions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Kanaka-io","download_url":"https://codeload.github.com/Kanaka-io/play-monadic-actions/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kanaka-io%2Fplay-monadic-actions/sbom","scorecard":{"id":76935,"data":{"date":"2025-08-11","repo":{"name":"github.com/Kanaka-io/play-monadic-actions","commit":"b41d573babd792a592ed851251a2cfcf265c5c2d"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.1,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":1,"reason":"Found 5/30 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 9 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-15T05:05:07.272Z","repository_id":24563321,"created_at":"2025-08-15T05:05:07.272Z","updated_at":"2025-08-15T05:05:07.272Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271378680,"owners_count":24749192,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-20T02:00:09.606Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dsl","monad","playframework","scala"],"created_at":"2024-11-13T16:44:19.817Z","updated_at":"2025-08-20T20:32:06.501Z","avatar_url":"https://github.com/Kanaka-io.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"Play monadic actions\n====================\n\n[![Build Status](https://travis-ci.org/Kanaka-io/play-monadic-actions.svg?branch=master)](https://travis-ci.org/Kanaka-io/play-monadic-actions) [![Gitter chat](https://badges.gitter.im/Kanaka-io/play-monadic-actions.png)](https://gitter.im/Kanaka-io/play-monadic-actions \"Gitter chat\") [![Coverage Status](https://coveralls.io/repos/github/Kanaka-io/play-monadic-actions/badge.svg?branch=master)](https://coveralls.io/github/Kanaka-io/play-monadic-actions?branch=master) \n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.kanaka/play-monadic-actions_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.kanaka/play-monadic-actions_2.13)\n\nThis little play module provides some syntactic sugar that allows boilerplate-free Actions using for-comprehensions.\n\n## Motivation\n\nIt is commonly admitted that controllers should be lean and only focus on parsing an incoming HTTP request, call (possibly many) service methods and finally build an HTTP response (preferably with a proper status). In the context of an asynchronous framework like Play!, most of these operations results are (or can be) wrapped in a `Future`, and since their outcome can be either positive or negative, these results have a type that is more or less isomorphic to `Future[Either[X, Y]]`. \n \nThis matter of facts raises some readability issues. Consider for example the following action : \n\n~~~scala\nclass ExampleController extends Controller {\n  \n  val beerOrderForm: Form[BeerOrder] = ???\n  def findAdultUser(id: String): Future[Either[UnderageError, User]] = ???\n  def sellBeer(beerName: String, customer: User): Future[Either[OutOfStockError, Beer]] = ???\n  \n  def orderBeer() = Action.async {\n     beerOrderForm.bindFromRequest().fold(\n       formWithErrors =\u003e BadRequest(views.html.orderBeer(formWithErrors),\n       beerOrder =\u003e \n        findAdultUser(beerOrder.userId).map(\n          _.fold(\n            ue =\u003e Conflict(displayError(ue)),\n            user =\u003e \n              sellBeer(beerOrder.beerName, user).map(\n                _.fold(\n                  oose =\u003e NotFound(displayError(oose)),\n                  beer =\u003e Ok(displayBeer(beer)  \n                )\n              )    \n          )\n        )\n  }\n}\n~~~\n\nThis is pretty straightforward, and yet the different *steps* of the computation are not made very clear. And since I've typed this in a regular text editor with no syntax highlighting nor static code analysis, there is an obvious error that you may not have spotted (there's a `map` instead of a `flatMap` somewhere). \n\nThis library addresses this problem by defining a `Step[A]` monad, which is roughly a `Future[Either[Result, A]]`, but with a right bias on the `Either` part, and providing a little DSL to lift relevant types into this monad's context.\n\nUsing it, the previous example becomes :\n\n~~~scala\nimport io.kanaka.monadic.dsl._\n\n// don't forget to import an implicit ExecutionContext\nimport play.api.libs.concurrent.Execution.Implicits.defaultContext \n\nclass ExampleController extends Controller {\n  \n  val beerOrderForm: Form[BeerOrder] = ???\n  def findAdultUser(id: String): Future[Either[UnderageError, User]] = ???\n  def sellBeer(beerName: String, customer: User): Future[Either[OutOfStockError, Beer]] = ???\n  \n  def orderBeer() = Action.async {\n    for {\n      beerOrder \u003c- beerOrderForm.bindFromRequest()    ?| (formWithErrors =\u003e BadRequest(views.html.orderBeer(formWithErrors))\n      user      \u003c- findAdultUser(beerOrder.userId)    ?| (ue =\u003e Conflict(displayError(ue))\n      beer      \u003c- sellBeer(beerOrder.beerName, user) ?| (oose =\u003e NotFound(displayError(oose))  \n    } yield Ok(displayBeer(beer))\n  }\n}\n~~~\n\n**IMPORTANT NOTE** : one **MUST** provide an implicit `ExecutionContext` for the DSL to work\n\n## How it works\n\nThe DSL introduces the binary `?|` operator. The happy path goes on the left hand side of the operator and the error path goes on the right : `happy ?| error`. Such expression produces a `Step[A]` which has all the required methods to make it usable in a for-comprehension. \n\nSo for example, if a service methods `foo`returns a `Future[Option[A]]`, we assume the happy path to be the case where the `Future` succeeds with a `Some[A]` and the error path to be the case where it succeeds with a `None` (the case where the `Future` fails is already taken care of by play's error handler). So we need to provide a proper `Result` to be returned in the error case (most probably a `NotFound`) and then we can write  \n\n~~~scala\nfor {\n // ...\n a \u003c- foo ?| NotFound    \n // ...\n} yield {\n // ...\n}\n~~~\n\nThe `a` here would be of type `A`, meaning that we've extracted the meaningful value from the `Future[Option[A]]` return by `foo`.\nOf course, if `foo` returns a `Future[None]` the for-comprehension is not evaluated further, and returns `NotFound`.\n\nThe right hand side of the `?|` operator (the error management part) is a function (or a thunk) that must return a `Result` and whose input type depends of the type of the expression on the left hand side of the operator (see the table of supported conversions below).\n\n## Filtering and Pattern-matching\n\n`Step[_]` defines a `withFilter` method, which means that one can use pattern matching and filtering in for-comprehensions involving `Step[_]`.\n\nFor example, if `bar` is of type `Future[Option[(Int, String)]]`, one can write \n\n~~~scala\nfor {\n (i, s) \u003c- bar ?| NotFound if s.length \u003e= i\n} yield Ok(s.take(i))\n~~~\n\nPlease note though that in the case where the predicate `s.length \u003e= i` does not hold, the whole `Future` will fail with a `NoSuchElementException`, and there is no easy way to transform this failure into a user-specified `Result`.\n\n## Supported conversions\n\nThe DSL supports the following conversions : \n\n| Defining module | Source type | Type of the right hand side | Type of the extracted value |\n| --- | --- | --- | --- |\n| `play-monadic-actions` | `Boolean` | `=\u003e Result` | `Unit` | \n| `play-monadic-actions` | `Option[A]` | `=\u003e Result` | `A` |\n| `play-monadic-actions` | `Try[A]` | `Throwable =\u003e Result` | `A` |\n| `play-monadic-actions` | `Either[B, A]` | `B =\u003e Result` | `A` |\n| `play-monadic-actions` | `Form[A]` | `Form[A] =\u003e Result` | `A` |\n| `play-monadic-actions` | `JsResult[A]` | `Seq[(JsPath, Seq[ValidationError])] =\u003e Result`| `A` |\n| `play-monadic-actions` | `Future[A]` | `Throwable =\u003e Result` | `A` |\n| `play-monadic-actions` | `Future[Boolean]` | `=\u003e Result` | `Unit` |\n| `play-monadic-actions` | `Future[Option[A]]` | `=\u003e Result` | `A` |\n| `play-monadic-actions` | `Future[Either[B, A]]` | `B =\u003e Result` | `A` |\n| `play-monadic-actions-cats` | `B Xor A` | `B =\u003e Result` | `A` |\n| `play-monadic-actions-cats` | `Future[B Xor A]` | `B =\u003e Result` | `A` |\n| `play-monadic-actions-cats` | `XorT[Future, B, A]` | `B =\u003e Result` | `A` |\n| `play-monadic-actions-cats` | `OptionT[Future, A]` | `=\u003e Result` | `A` |\n| `play-monadic-actions-cats` | `Validated[B Xor A]` | `B =\u003e Result` | `A` |\n| `play-monadic-actions-cats` | `Future[Validated[B Xor A]]` | `B =\u003e Result` | `A` |\n| `play-monadic-actions-scalaz-7-1`  | `B \\/ A` |  `B =\u003e Result` | `A` |\n| `play-monadic-actions-scalaz-7-1` | `Future[B \\/ A]` |  `B =\u003e Result` | `A` |\n| `play-monadic-actions-scalaz-7-1` | `Validation[B, A]` |  `B =\u003e Result` | `A` |\n| `play-monadic-actions-scalaz-7-1` | `EitherT[Future, B, A]` |  `B =\u003e Result` | `A` |\n| `play-monadic-actions-scalaz-7-1` | `OptionT[Future, A]` |  `Unit =\u003e Result` | `A` |\n| `play-monadic-actions-scalaz-7-1` | `Future[Validation[B, A]]` |  `B =\u003e Result` | `A` |\n| `play-monadic-actions-scalaz-7-2` | `B \\/ A` |  `B =\u003e Result` | `A` |\n| `play-monadic-actions-scalaz-7-2` | `Future[B \\/ A]` |  `B =\u003e Result` | `A` |\n| `play-monadic-actions-scalaz-7-2` | `Validation[B, A]` |  `B =\u003e Result` | `A` |\n| `play-monadic-actions-scalaz-7-2` | `EitherT[Future, B, A]` |  `B =\u003e Result` | `A` |\n| `play-monadic-actions-scalaz-7-2` | `OptionT[Future, A]` |  `Unit =\u003e Result` | `A` |\n| `play-monadic-actions-scalaz-7-2` | `Future[Validation[B, A]]` |  `B =\u003e Result` | `A` |\n\n\n## Installation\n\nAs of version `2.2.1`, all modules are published for Scala versions `2.11` up to `2.13`.\n\nUsing sbt :\n\nCurrent version is 2.2.0\n~~~scala\nlibraryDependencies += \"io.kanaka\" %% \"play-monadic-actions\" % \"2.2.0\"\n~~~\n\nThere are also contrib modules for interoperability with scalaz and cats : \n\n|module name|is compatible with / built against|\n| --- | --- |\n|play-monadic-actions-cats| cats 2.0.0|\n|play-monadic-actions-scalaz_7-2| scalaz 7.2.28|\n\nEach of these module provides `Functor` and `Monad` instances for `Step[_]` as well as conversions for relevant types in the target library \n\nThese instances and conversions are made available by importing `io.kanaka.monadic.dsl.compat.cats._` and `io.kanaka.monadic.dsl.compat.scalaz._` respectively.\n \n## Compatibility\n\n- Version `2.2.0` is compatible with Play! `2.7.x`\n- Version `2.1.0` is compatible with Play! `2.6.x`\n- Version `2.0.0` is compatible with Play! `2.5.x`\n- Version `1.1.0` is compatible with Play! `2.4.x`\n- Version `1.0.1` is compatible with Play! `2.3.x`\n\nFrom version `2.0.0` up, dependencies toward play and cats are defined as `provided`, meaning that you can use the DSL along with any version of these projects you see fit. The sample projects under `samples/` demonstrate this capability.\n\nFrom version `2.1.0` up, the modules are published for scala `2.11` and `2.12`. Previous versions are only published for scala `2.11`. \n\n## Contributors\n\n[Valentin Kasas](https://twitter.com/ValentinKasas)\n\n[Damien Gouyette](https://twitter.com/cestpasdur)\n\n[David R. Bild](https://github.com/drbild)\n\n[Bjørn Madsen](https://github.com/aeons)\n\n[Christophe Calves](https://github.com/christophe-calves)\n\n[Maxim Karpov](https://github.com/makkarpov)\n\n[Richard Searle](https://github.com/searler)\n\n[Andrew Adams](https://github.com/adamsar)\n\n... your name here\n\n## Credits\n\nThis project is widely inspired from the [play-monad-transformers activator template](https://github.com/lunatech-labs/play-monad-transformers#master) by Lunatech.\n\nIt also uses [coursier](https://github.com/alexarchambault/coursier) to fetch dependencies in parallel, which is a pure bliss. Take a look if you don't know it yet. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkanaka-io%2Fplay-monadic-actions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkanaka-io%2Fplay-monadic-actions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkanaka-io%2Fplay-monadic-actions/lists"}