{"id":43280148,"url":"https://github.com/tosun-si/asgarde","last_synced_at":"2026-02-01T17:04:10.972Z","repository":{"id":38370681,"uuid":"351949289","full_name":"tosun-si/asgarde","owner":"tosun-si","description":"Asgarde allows simplifying error handling with Apache Beam Java, with less code, more concise and expressive code.","archived":false,"fork":false,"pushed_at":"2025-12-19T15:09:47.000Z","size":134,"stargazers_count":86,"open_issues_count":2,"forks_count":5,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-12-21T03:11:16.237Z","etag":null,"topics":["apache-beam","cloud-dataflow","error-handling","google-cloud-platform","java","kotlin"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tosun-si.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-03-27T00:54:31.000Z","updated_at":"2025-12-19T15:09:11.000Z","dependencies_parsed_at":"2025-12-19T07:03:53.191Z","dependency_job_id":null,"html_url":"https://github.com/tosun-si/asgarde","commit_stats":null,"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"purl":"pkg:github/tosun-si/asgarde","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tosun-si%2Fasgarde","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tosun-si%2Fasgarde/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tosun-si%2Fasgarde/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tosun-si%2Fasgarde/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tosun-si","download_url":"https://codeload.github.com/tosun-si/asgarde/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tosun-si%2Fasgarde/sbom","scorecard":{"id":895319,"data":{"date":"2025-08-11","repo":{"name":"github.com/tosun-si/asgarde","commit":"ad713bb84ce18e63c29a234cd3c02568de5740bc"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.1,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","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":"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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/build-project.yml:1","Warn: no topLevel permission defined: .github/workflows/publish-project.yml:1","Info: no jobLevel write permissions found"],"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":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","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":"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":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-project.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/tosun-si/asgarde/build-project.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-project.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/tosun-si/asgarde/build-project.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish-project.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/tosun-si/asgarde/publish-project.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish-project.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/tosun-si/asgarde/publish-project.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish-project.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/tosun-si/asgarde/publish-project.yml/main?enable=pin","Info:   0 out of   5 GitHub-owned GitHubAction dependencies pinned"],"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"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":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"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":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/publish-project.yml:10"],"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":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 7 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"}},{"name":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-j288-q9x7-2f5v"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T13:28:18.069Z","repository_id":38370681,"created_at":"2025-08-24T13:28:18.069Z","updated_at":"2025-08-24T13:28:18.069Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28983431,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T16:29:42.054Z","status":"ssl_error","status_checked_at":"2026-02-01T16:29:41.428Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["apache-beam","cloud-dataflow","error-handling","google-cloud-platform","java","kotlin"],"created_at":"2026-02-01T17:04:10.889Z","updated_at":"2026-02-01T17:04:10.959Z","avatar_url":"https://github.com/tosun-si.png","language":"Java","funding_links":[],"categories":["Data Transformation"],"sub_categories":["Distributed Computing"],"readme":"![Logo](asgarde_logo_small.gif) \n\n# Asgarde\n\nThis module allows simplifying error handling with Apache Beam Java.\n\n## Versions compatibility between Beam and Asgarde\n\n\u003cdiv style=\"max-height: 200px; overflow-y: auto; overflow-x: auto;\"\u003e\n\n| Asgarde | Beam   |\n|---------|--------|\n| 0.10.0  | 2.31.0 |\n| 0.11.0  | 2.32.0 |\n| 0.12.0  | 2.33.0 |\n| 0.13.0  | 2.34.0 |\n| 0.14.0  | 2.35.0 |\n| 0.15.0  | 2.36.0 |\n| 0.16.0  | 2.37.0 |\n| 0.17.0  | 2.38.0 |\n| 0.18.0  | 2.39.0 |\n| 0.19.0  | 2.40.0 |\n| 0.20.0  | 2.41.0 |\n| 0.21.0  | 2.42.0 |\n| 0.22.0  | 2.43.0 |\n| 0.23.0  | 2.44.0 |\n| 0.24.0  | 2.45.0 |\n| 0.25.0  | 2.46.0 |\n| 0.26.0  | 2.47.0 |\n| 0.27.0  | 2.48.0 |\n| 0.28.0  | 2.49.0 |\n| 0.29.0  | 2.50.0 |\n| 0.30.0  | 2.51.0 |\n| 0.31.0  | 2.52.0 |\n| 0.32.0  | 2.53.0 |\n| 0.33.0  | 2.54.0 |\n| 0.34.0  | 2.55.0 |\n| 0.35.0  | 2.56.0 |\n| 0.36.0  | 2.57.0 |\n| 0.37.0  | 2.58.0 |\n| 0.38.0  | 2.59.0 |\n| 0.39.0  | 2.60.0 |\n| 0.40.0  | 2.61.0 |\n| 0.41.0  | 2.62.0 |\n| 0.42.0  | 2.63.0 |\n| 0.43.0  | 2.64.0 |\n| 0.44.0  | 2.65.0 |\n| 0.45.0  | 2.66.0 |\n| 0.46.0  | 2.67.0 |\n| 0.47.0  | 2.68.0 |\n| 0.48.0  | 2.69.0 |\n| 0.49.0  | 2.70.0 |\n\n\u003c/div\u003e\n\n## Installation of project\n\nThe project is hosted on Maven repository.\\\nYou can install it with all the build tools compatibles with Maven.\n\nExample with Maven and Gradle :\n\n#### Maven\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003efr.groupbees\u003c/groupId\u003e\n    \u003cartifactId\u003easgarde\u003c/artifactId\u003e\n    \u003cversion\u003e0.49.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n#### Gradle\n\n```text\nimplementation group: 'fr.groupbees', name: 'asgarde', version: '0.49.0'\n```\n\n## Error logic with Beam ParDo and DoFn\n\nBeam recommends treating errors with Dead letters.\\\nIt means catching errors in the flow and, using side outputs, sinking errors to a file, database or any other output...\n\nBeam suggests handling side outputs with `TupleTags` in a `DoFn` class, example :\n\n```java\n// Failure object.\npublic class Failure implements Serializable {\n    private final String pipelineStep;\n    private final Integer inputElement;\n    private final Throwable exception;\n\n    public static \u003cT\u003e Failure from(final String pipelineStep,\n                                   final T element,\n                                   final Throwable exception) {\n        return new Failure(pipelineStep, element.toString(), exception);\n    }\n}\n\n// Word count DoFn class.\npublic class WordCountFn extends DoFn\u003cString, Integer\u003e {\n\n     private final TupleTag\u003cInteger\u003e outputTag = new TupleTag\u003cInteger\u003e() {};\n     private final TupleTag\u003cFailure\u003e failuresTag = new TupleTag\u003cFailure\u003e() {};\n    \n     @ProcessElement\n     public void processElement(ProcessContext ctx) {\n        try {\n            // Could throw ArithmeticException.\n            final String word = ctx.element();\n            ctx.output(1 / word.length());\n        } catch (Throwable throwable) {\n            final Failure failure = Failure.from(\"step\", ctx.element(), throwable);\n            ctx.output(failuresTag, failure);\n        }\n    }\n    \n    public TupleTag\u003cInteger\u003e getOutputTag() {\n       return outputTag;\n    }\n    \n    public TupleTag\u003cFailure\u003e getFailuresTag() {\n       return failuresTag;\n    }\n}\n```\n\n```java\n// In Beam pipeline flow.\nfinal PCollection\u003cString\u003e wordPCollection....\n\nfinal WordCountFn wordCountFn = new WordCountFn();\n\nfinal PCollectionTuple tuple = wordPCollection\n           .apply(\"ParDo\", ParDo.of(wordCountFn).withOutputTags(wordCountFn.getOutputTag(), TupleTagList.of(wordCountFn.getFailuresTag())));\n\n// Output PCollection via outputTag.\nPCollection\u003cInteger\u003e outputCollection = tuple.get(wordCountFn.getOutputTag());\n\n// Failures PCollection via failuresTag.\nPCollection\u003cFailure\u003e failuresCollection = tuple.get(wordCountFn.getFailuresTag());\n```\n\nWith this approach we can, in all steps, get the output and failures result PCollections. \n\n## Error logic with Beam MapElements and FlatMapElements\n\nBeam also allows handling errors with built-in components like `MapElements` and `FlatMapElements` (it's currently an experimental feature as of april of 2020).\n\nBehind the scene, in these classes Beam use the same concept explained above.\n\nExample: \n\n```java\npublic class Failure implements Serializable {\n    private final String pipelineStep;\n    private final String inputElement;\n    private final Throwable exception;\n\n    public static \u003cT\u003e Failure from(final String pipelineStep,\n                                   final WithFailures.ExceptionElement\u003cT\u003e exceptionElement) {\n        final T inputElement = exceptionElement.element();\n        return new Failure(pipelineStep, inputElement.toString(), exceptionElement.exception());\n    }\n}\n\n// In Beam pipeline flow.\nfinal PCollection\u003cString\u003e wordPCollection....\n\nWithFailures.Result\u003cPCollection\u003cInteger\u003e, Failure\u003e result = wordPCollection\n   .apply(\"Map\", MapElements\n           .into(TypeDescriptors.integers())\n           .via((String word) -\u003e 1 / word.length())  // Could throw ArithmeticException\n           .exceptionsInto(TypeDescriptor.of(Failure.class))\n           .exceptionsVia(exElt -\u003e Failure.from(\"step\", exElt))\n   );\n\nPCollection\u003cString\u003e output = result.output();\nPCollection\u003cFailure\u003e failures = result.failures();\n```\n\nThe logic is the same for FlatMapElements : \n\n```java\nfinal PCollection\u003cString\u003e wordPCollection....\n\nWithFailures.Result\u003cPCollection\u003cString\u003e, Failure\u003e\u003e result = wordPCollection\n   .apply(\"FlatMap\", FlatMapElements\n           .into(TypeDescriptors.strings())\n           .via((String line) -\u003e Arrays.asList(Arrays.copyOfRange(line.split(\" \"), 1, 5)))\n           .exceptionsInto(TypeDescriptor.of(Failure.class))\n           .exceptionsVia(exElt -\u003e Failure.from(\"step\", exElt))\n   )\n\nPCollection\u003cString\u003e output = result.output();\nPCollection\u003cFailure\u003e failures = result.failures();\n```\n\n## Comparison between approaches\n\n### Usual Beam pipeline\n\nIn a usual Beam pipeline flow, steps are chained fluently: \n\n```java\nfinal PCollection\u003cInteger\u003e outputPCollection = inputPCollection\n                 .apply(\"Map\", MapElements .into(TypeDescriptors.strings()).via((String word) -\u003e word + \"Test\"))\n                 .apply(\"FlatMap\", FlatMapElements\n                                         .into(TypeDescriptors.strings())\n                                         .via((String line) -\u003e Arrays.asList(Arrays.copyOfRange(line.split(\" \"), 1, 5))))\n                 .apply(\"Map ParDo\", ParDo.of(new WordCountFn()));\n```\n\n### Usual Beam pipeline with error handling\n\nHere's the same flow with error handling in each step:\n\n```java\nWithFailures.Result\u003cPCollection\u003cString\u003e, Failure\u003e result1 = input\n        .apply(\"Map\", MapElements\n                        .into(TypeDescriptors.strings())\n                        .via((String word) -\u003e word + \"Test\")\n                        .exceptionsInto(TypeDescriptor.of(Failure.class))\n                        .exceptionsVia(exElt -\u003e Failure.from(\"step\", exElt)));\n\nfinal PCollection\u003cString\u003e output1 = result1.output();\nfinal PCollection\u003cFailure\u003e failure1 = result1.failures();\n\nWithFailures.Result\u003cPCollection\u003cString\u003e, Failure\u003e result2 = output1\n        .apply(\"FlatMap\", FlatMapElements\n                            .into(TypeDescriptors.strings())\n                            .via((String line) -\u003e Arrays.asList(Arrays.copyOfRange(line.split(\" \"), 1, 5)))\n                            .exceptionsInto(TypeDescriptor.of(Failure.class))\n                            .exceptionsVia(exElt -\u003e Failure.from(\"step\", exElt)));\n\nfinal PCollection\u003cString\u003e output2 = result1.output();\nfinal PCollection\u003cFailure\u003e failure2 = result1.failures();\n\nfinal PCollectionTuple result3 = output2\n        .apply(\"Map ParDo\", ParDo.of(wordCountFn).withOutputTags(wordCountFn.getOutputTag(), TupleTagList.of(wordCountFn.getFailuresTag())));\n\nfinal PCollection\u003cInteger\u003e output3 = result3.get(wordCountFn.getOutputTag());\nfinal PCollection\u003cFailure\u003e failure3 = result3.get(wordCountFn.getFailuresTag());\n\nfinal PCollection\u003cFailure\u003e allFailures = PCollectionList\n        .of(failure1)\n        .and(failure2)\n        .and(failure3)\n        .apply(Flatten.pCollections());\n```\n\nProblems with this approach: \n\n- We loose the native fluent style on apply chains, because we have to handle output and error for each step.\n- For `MapElements` and `FlatMapElements` we have to always add `exceptionsInto` and `exceptionsVia` (can be centralized).\n- For each custom DoFn, we have to duplicate the code of `TupleTag` logic and the try catch block (can be centralized).\n- The code is verbose.\n- There is no centralized code to concat all the errors, we have to concat all failures (can be centralized).\n \n\n### Usual Beam pipeline with error handling using Asgarde\n\nHere's the same flow with error handling, but using this library instead:\n\n```java\nfinal WithFailures.Result\u003cPCollection\u003cInteger\u003e, Failure\u003e resultComposer = CollectionComposer.of(input)\n        .apply(\"Map\", MapElements.into(TypeDescriptors.strings()).via((String word) -\u003e word + \"Test\"))\n        .apply(\"FlatMap\", FlatMapElements\n                .into(TypeDescriptors.strings())\n                .via((String line) -\u003e Arrays.asList(Arrays.copyOfRange(line.split(\" \"), 1, 5))))\n        .apply(\"ParDo\", MapElementFn.into(TypeDescriptors.integers()).via(word -\u003e 1 / word.length()))\n        .getResult();\n```\n\nSome explanations:\n- The `CollectionComposer` class allows to centralize all the error logic, fluently compose the applies and concat all the failures occurring in the flow. \n- For `MapElements` and `FlatMapElements`, behind the scene, the `apply` method adds `exceptionsInto` and `exceptionsVia` on `Failure` object. \nWe can also explicitely use `exceptionsInto` and `exceptionsVia` if needed, if you have some custom logic based on `Failure` object.\n- The `MapElementFn` class is a custom `DoFn` class internally wraps the shared logic for `DoFn` like try/catch block and Tuple tags.\nWe will detail concepts in the next sections.\n\n\n## Purpose of the library\n\n- Wrap all error handling logic in a composer class.\n- Wrap `exceptionsInto` and `exceptionsVia` usage in the native Beam classes `MapElements` and `FlatMapElements`.\n- Keep the fluent style natively proposed by Beam in `apply` methods while checking for failures and offer a less verbose way of handling errors.\n- Expose custom `DoFn` classes with centralized try/catch blocks (loan pattern) and Tuple tags.\n- Expose an easier access to the `@Setup`, `@StartBundle`, `@FinishBundle`, `@Teardown` steps of `DoFn` classes.\n- Expose a way to handle errors in filtering logic (currently not available with Beam's `Filter.by`).\n\nSome resources for Loan pattern : \n\nhttps://dzone.com/articles/functional-programming-patterns-with-java-8\n\nhttps://blog.knoldus.com/scalaknol-understanding-loan-pattern/\n\n### Custom DoFn classes\n\n#### `MapElementFn`\n\nThis class is the equivalent of a Beam `MapElements`.\n\nIt must be created by the `outputTypeDescriptor` and takes a `SerializableFunction` on generic input and output (input and output types of the mapper).\n\nThis `SerializableFunction` will be invoked lazily in the `@ProcessElement` method and lifecycle of the `DoFn`.\n\nWe can also give to this class actions related to `DoFn` lifecycle :\n- `setupAction` executed in the `@Setup` method\n- `startBundleAction` executed in the `@StartBundle` method\n- `finishBundleAction` executed in the `@FinishBundle` method\n- `teardownAction` executed in the `@Teardown` method\n\nThis action is represented by a `SerializableAction`: \n\n```java\n@FunctionalInterface\npublic interface SerializableAction extends Serializable {\n    \n    void execute();\n}\n```\n\nThe `SerializableAction` is like a `java.lang.Runnable` that has to implement `Serializable`.\n\nWhen writing a `DoFn`, Beam can infer the output type from `DoFn\u003cInput, Output\u003e` and deduce the output type descriptor from it. \n\nA default `Coder` can be added for this descriptor.\n\nWhen we write a generic `DoFn`, Beam is unable to infer the output type and create the output descriptor, \nthat's why in our custom `DoFn` classes we have to give the output descriptor in the `into` method.\n\nUsage example:\n\n```java\nfinal PCollection\u003cInteger\u003e outputMapElementFn = CollectionComposer.of(inputPCollection)\n        .apply(\"PaDo\", MapElementFn\n                .into(TypeDescriptors.integers())\n                .via((String word) -\u003e 1 / word.length())\n                .withSetupAction(() -\u003e LOGGER.info(\"Start word count action in the worker\"))\n                .withTeardownAction(() -\u003e LOGGER.info(\"End word count action in the worker\")))\n        .getResult()\n        .output();\n```\n\nBehind the scene the `CollectionComposer` class adds a `ParDo` on this `DoFn` and handles errors with tuple tags.\n\n#### `MapProcessElementFn` \n\nThis class works exactly as `MapElementFn`, but gives access to Beam's `ProcessContext` object.\n\nIt must be created from the input type of `DoFn`, which allows giving more information on the input, \nbecause the `SerializableFunction` is from the `ProcessContext` and not from the `Input` (doesn't bring the input type):\n\n```java\nSerializableFunction\u003cProcessContext, Output\u003e\n```\n\nThis class can take a `DoFn` lifecycle methods as the `MapElementFn` : \n- `setupAction`\n- `startBundleAction`\n- `finishBundleAction`\n- `teardownAction`\n\nand expects an output descriptor too.\n\nUsage example: \n\n```java\nfinal PCollection\u003cInteger\u003e resMapProcessElementFn = CollectionComposer.of(input)\n        .apply(\"PaDo\", MapProcessContextFn\n                .from(String.class)\n                .into(TypeDescriptors.integers())\n                .via(ctx -\u003e 1 / ctx.element().length())\n                .withSetupAction(() -\u003e LOGGER.info(\"Setup word count action in the worker\"))\n                .withStartBundleAction(() -\u003e LOGGER.info(\"Start bundle word count action in the worker\"))\n                .withFinishBundleAction(() -\u003e LOGGER.info(\"Finish bundle word count action in the worker\"))\n                .withTeardownAction(() -\u003e LOGGER.info(\"End word count action in the worker\"))))\n        .getResult()\n        .output();\n```\n\nSometimes we need access to Beam's `ProcessContext` to get technical fields or handle side inputs.\n\nFor `MapElementFn` and `MapProcessContextFn`, we can give side inputs to the `CollectionComposer`.\n\nHere's an example using side inputs:\n\n```java\n// Simulates a side input, from Beam pipeline.\nfinal String wordDescription = \"Word to describe Football teams\";\n\nfinal PCollectionView\u003cString\u003e sideInputs = pipeline\n        .apply(\"Create word description side input\", Create.of(wordDescription))\n        .apply(\"Create as collection view\", View.asSingleton());\n\nfinal PCollection\u003cWordStats\u003e resMapProcessElementFn = CollectionComposer.of(input)\n        .apply(\"PaDo\", MapProcessContextFn\n                        .from(String.class)\n                        .into(TypeDescriptor.of(WordStats.class))\n                        .via(ctx -\u003e toWordStats(sideInputs, ctx))\n                        .withSetupAction(() -\u003e LOGGER.info(\"Start word count action in the worker\")),\n                \n                Collections.singleton(sideInputs))\n        .getResult()\n        .output();\n```\n\n```java\nprivate WordStats toWordStats(final PCollectionView\u003cString\u003e sideInputs, final DoFn\u003cString, WordStats\u003e.ProcessContext context) {\n    final String word = context.element();\n    final Integer dividedWordCount = 1 / word.length();\n\n    // Gets the current timestamp in context.\n    final Instant timestamp = context.timestamp();\n\n    // Gets the side input value from PCollectionView and context.\n    final String wordDescription = context.sideInput(sideInputs);\n\n    return new WordStats(word, dividedWordCount, timestamp, wordDescription);\n}\n\nprivate static class WordStats implements Serializable {\n    private final String word;\n    private final Integer dividedWordCount;\n    private final Instant timestamp;\n    private final String wordDescription;\n\n    public WordStats(String word, Integer dividedWordCount, Instant timestamp, String wordDescription) {\n        this.word = word;\n        this.dividedWordCount = dividedWordCount;\n        this.timestamp = timestamp;\n        this.wordDescription = wordDescription;\n    }\n}\n```\n\nBehind the scene the `CollectionComposer` class adds the `ParDo` on this `DoFn` and handles errors with tuple tags.\n\n#### `FlatMapElementFn`\n\nSame principle as `MapElementFn` but for `flatMap` operator.\n\n```java\nPCollection\u003cPlayer\u003e players =\n    FlatMapElementFn.into(TypeDescriptor.of(Player.class))\n        .via(team -\u003e team.getPlayers())\n        .withSetupAction(() -\u003e System.out.println(\"Starting of mapping...\")\n        .withStartBundleAction(() -\u003e System.out.println(\"Starting bundle of mapping...\")\n        .withFinishBundleAction(() -\u003e System.out.println(\"Ending bundle of mapping...\")\n        .withTeardownAction(() -\u003e System.out.println(\"Ending of mapping...\")\n```\n\n#### `FlatMapProcessContextFn`\n\nSame principle as `MapProcessContextFn` but for `flatMap` operator.\n\n```java\nPCollection\u003cPlayer\u003e players =\n    FlatMapProcessContextFn.from(Team.class)\n        .into(TypeDescriptor.of(Player.class))\n        .via((ProcessContext ctx) -\u003e ctx.element().getPlayers())\n        .withSetupAction(() -\u003e System.out.println(\"Starting of mapping...\")\n        .withStartBundleAction(() -\u003e System.out.println(\"Starting bundle of mapping...\")\n        .withFinishBundleAction(() -\u003e System.out.println(\"Ending bundle of mapping...\")\n        .withTeardownAction(() -\u003e System.out.println(\"Ending of mapping...\")\n```\n\n#### `FilterFn` \n\nThis class is like the `Filter` class exposed by Beam but with built-in error handling.\nIt's a generic `DoFn` implementation just like `MapElementFn` and `MapProcessElementFn`.\n\nUsage example:\n\n```java\nfinal PCollection\u003cString\u003e resFilterFn = CollectionComposer.of(wordCollection)\n            .apply(\"FilterFn\", FilterFn.by(word -\u003e word.length() \u003e 3))\n            .getResult()\n            .output();\n```\n\nIt takes a predicate via: \n\n```java\nfinal SerializableFunction\u003cInputT, Boolean\u003e predicate\n```\n\nBehind the scene, the `CollectionComposer` takes the output from the previous `PCollection`.\nNo need to pass any output descriptor in this case, because it's only a filtering operation on the same type.\n\n\n### Collection composer class\n\nCentralizes error handling in one place.\n\nCreate an instance from a PCollection.\n\n```java\nCollectionComposer.of(wordPCollection)\n```\n\n\n* It accepts native `MapElements` and `FlatMapElements` without adding `exceptionsType` and `exceptionsVia`:\n\n```java\nfinal PCollection\u003cInteger\u003e resMapElements = CollectionComposer.of(input)\n        .apply(\"ParDo\", MapElements.into(TypeDescriptors.integers()).via((String word) -\u003e 1 / word.length()))\n        .getResult()\n        .output();\n\nfinal PCollection\u003cString\u003e resFlatMapElements = CollectionComposer.of(input)\n        .apply(\"FlatMapElements\", FlatMapElements\n                .into(TypeDescriptors.strings())\n                .via((String line) -\u003e Arrays.asList(Arrays.copyOfRange(line.split(\" \"), 1, 5))))\n        .getResult()\n        .output();\n```\n\n* It accepts native `MapElements` and `FlatMapElements` with `exceptionsType` and `exceptionsVia` \n(if external` exceptionsType` and `exceptionsVia` are given, they have to be based on the `Failure` object exposed by \nthe library):\n\n```java\nfinal PCollection\u003cInteger\u003e resMapElements2 = CollectionComposer.of(input)\n        .apply(\"MapElements\", MapElements\n                .into(TypeDescriptors.integers())\n                .via((String word) -\u003e 1 / word.length())\n                .exceptionsInto(of(Failure.class))\n                .exceptionsVia(exElt -\u003e Failure.from(\"step\", exElt)))\n        .getResult()\n        .output();\n\nfinal PCollection\u003cString\u003e resFlatMapElements2 = CollectionComposer.of(input)\n        .apply(\"FlatMapElements\", FlatMapElements\n                .into(TypeDescriptors.strings())\n                .via((String line) -\u003e Arrays.asList(Arrays.copyOfRange(line.split(\" \"), 1, 5)))\n                .exceptionsInto(of(Failure.class))\n                .exceptionsVia(exElt -\u003e Failure.from(\"step\", exElt)))\n        .getResult()\n        .output();\n```\n\nThe `Failure` object exposed by the library, contains the `inputElement` of the step in a String format (toString method), and the occurred exception.\nFor the input element used in transforms, developers can override toString method and implement the string representation of object.\nA possible approach is to serialize the object to Json string via a framework like Jackson for better readability.\nBeam already uses Jackson as an internal dependency.\n\nExample with a `Team` class used in a Beam Transform : \n\n```java\npublic static class Team implements Serializable {\n    ...\n\n    @Override\n    public String toString() {\n        return JsonUtil.serialize(this);\n    }\n}\n\npublic static \u003cT\u003e String serialize(final T obj) {\n    try {\n        return OBJECT_MAPPER.writeValueAsString(obj);\n    } catch (JsonProcessingException e) {\n        throw new IllegalStateException(\"The serialization of object fails : \" + obj);\n    }\n}\n```\n\nIn this case, we use Jackson to serialize the current object to Json String, in the toString method of the Team object.\n\n \nThe Failure class exposes factory methods to build from inputs:\n\n```java\npublic class Failure implements Serializable {\n    private final String pipelineStep;\n    private final String inputElement;\n    private final Throwable exception;\n\n    private Failure(String pipelineStep,\n                    String inputElement,\n                    Throwable exception) {\n        this.pipelineStep = pipelineStep;\n        this.inputElement = inputElement;\n        this.exception = exception;\n    }\n\n    public static \u003cT\u003e Failure from(final String pipelineStep,\n                                   final WithFailures.ExceptionElement\u003cT\u003e exceptionElement) {\n        final T inputElement = exceptionElement.element();\n        return new Failure(pipelineStep, inputElement.toString(), exceptionElement.exception());\n    }\n    \n    public static \u003cT\u003e Failure from(final String pipelineStep,\n                                   final T element,\n                                   final Throwable exception) {\n        return new Failure(pipelineStep, element.toString(), exception);\n    }\n}\n```\n\n* It accepts `MapElementFn`, `MapProcessElementFn`, `MapElementFn`, `MapProcessElementFn`, `FilterFn` and custom `DoFn`s:\n\n```java\n// Map element Fn.\nfinal PCollection\u003cInteger\u003e resMapElementFn = CollectionComposer.of(input)\n            .apply(\"PaDo\", MapElementFn\n                    .into(TypeDescriptors.integers())\n                    .via((String word) -\u003e 1 / word.length())\n                    .withSetupAction(() -\u003e LOGGER.info(\"Start word count action in the worker\")))\n            .getResult()\n            .output();\n\n// Map process context Fn without side inputs.\nfinal PCollection\u003cInteger\u003e resMapProcessElementFn = CollectionComposer.of(input)\n            .apply(\"PaDo\", MapProcessContextFn\n                    .from(String.class)\n                    .into(TypeDescriptors.integers())\n                    .via(ctx -\u003e 1 / ctx.element().length())\n                    .withSetupAction(() -\u003e LOGGER.info(\"Start word count action in the worker\")))\n            .getResult()\n            .output();\n\n\n// Map process context Fn with side inputs.\nfinal String wordDescription = \"Word to describe Football teams\";\n\nfinal PCollectionView\u003cString\u003e sideInputs = input.getPipeline()\n            .apply(\"String side input\", Create.of(wordDescription))\n            .apply(\"Create as collection view\", View.asSingleton());\n\nfinal PCollection\u003cWordStats\u003e resMapProcessElementFn2 = CollectionComposer.of(input)\n            .apply(\"PaDo\", MapProcessContextFn\n                            .from(String.class)\n                            .into(TypeDescriptor.of(WordStats.class))\n                            .via(ctx -\u003e toWordStats(sideInputs, ctx))\n                            .withSetupAction(() -\u003e LOGGER.info(\"Start word count action in the worker\")),\n    \n                    Collections.singleton(sideInputs))\n            .getResult()\n            .output();\n\n// Filter Fn.\nfinal PCollection\u003cString\u003e resFilterFn = CollectionComposer.of(input)\n            .apply(\"FilterFn\", FilterFn.by(word -\u003e word.length() \u003e 3))\n            .getResult()\n            .output();\n```\n\n* Custom non-generic `DoFn` classes are also supported, they must extend the `BaseElementFn` (it initializes the tuple tags logic) \nand the type descriptors will be deduced from non-generic types:\n\n```java\nimport fr.groupbees.asgarde.Failure;\n\nfinal PCollection\u003cWordStats\u003e resCustomDoFn=CollectionComposer.of(input)\n        .apply(\"PaDo\",new WordStatsFn(sideInputs),Collections.singleton(sideInputs))\n        .getResult()\n        .output();\n\n// Custom DoFn class.\npublic class WordStatsFn extends BaseElementFn\u003cString, WordStats\u003e {\n\n    private PCollectionView\u003cString\u003e sideInputs;\n\n    public WordStatsFn(final PCollectionView\u003cString\u003e sideInputs) {\n        // Do not forget to call this!\n        super();\n\n        this.sideInputs = sideInputs\n    }\n\n    @ProcessElement\n    public void processElement(ProcessContext ctx) {\n        try {\n            ctx.output(toWordStats(sideInputs, ctx));\n        } catch (Throwable throwable) {\n            final Failure failure = Failure.from(\"step\", ctx.element(), throwable);\n            ctx.output(failuresTag, failure);\n        }\n    }\n}\n```\n\n* It keeps the `apply` methods' fluent style and internally concats all the occurring failures.\n\n```java\nfinal WithFailures.Result\u003cPCollection\u003cInteger\u003e, Failure\u003e resultComposer = CollectionComposer.of(input)\n            .apply(\"Map\", MapElements.into(TypeDescriptors.strings()).via((String word) -\u003e word + \"Test\"))\n            .apply(\"FlatMap\", FlatMapElements\n                    .into(TypeDescriptors.strings())\n                    .via((String line) -\u003e Arrays.asList(Arrays.copyOfRange(line.split(\" \"), 1, 5))))\n            .apply(\"ParDo\", MapElementFn.into(TypeDescriptors.integers()).via(word -\u003e 1 / word.length()))\n            .getResult();\n\nfinal PCollection\u003cString\u003e output = resultComposer.output();\nfinal PCollection\u003cFailure\u003e failures = resultComposer.failures();\n```\n\n### Coder in CollectionComposer class\n\n#### Coder for output PCollection\n\nA coder can be set for the current output `PCollection` in the flow, example : \n\n```java\n// Generate and instantiate an Avro specific object.\nfinal AvroTest inputAvroObject = AvroTest.newBuilder()\n    .setId(34444)\n    .setName(\"test\")\n    .build();\n\n// Read an Avro file with a type corresponding to the previous specific object.\nfinal PCollection\u003cAvroTest\u003e avroObjectsCollection = pipeline.apply(\"Reads Avro objects\", AvroIO\n    .read(AvroTest.class)\n    .from(\"filePath\"));\n\nprivate GenericRecord avroObjectToGenericRecord(final AvroTest avroTest) {\n    GenericRecord record = new GenericData.Record(avroTest.getSchema());\n    record.put(\"id\", avroTest.getId());\n    record.put(\"name\", avroTest.getName());\n\n    return record;\n}\n\n// Flow with CollectionComposer.\nfinal Result\u003cPCollection\u003cGenericRecord\u003e, Failure\u003e result2 = CollectionComposer.of(avroObjectsCollection)\n    .apply(\"Map\", MapElements.into(of(GenericRecord.class)).via(this::avroObjectToGenericRecord))\n    .setCoder(AvroCoder.of(GenericRecord.class, inputAvroObject.getSchema()))\n    .getResult();\n```\n\nIn this example, an `Avro` file is read and mapped to a typed and specific object `AvroTest`.\\\nThen we simulate a transformation from this `AvroTest` instance to a `GenericRecord` object.\\\nThe `GenericRecord` doesn't contain any information about `Serialization` and in this case `Beam` can't infer\na default `Coder`.\n\nWe have to set a `Coder` for the output `PCollection` of `GenericRecord` :\n\n```java\n  .setCoder(AvroCoder.of(GenericRecord.class, inputAvroObject.getSchema()))\n```\n\n`Asgarde` and the `CollectionComposer` class work as the usual `PCollection` for coders and \npropose the same `setCoder` method for good outputs.\n\n\n## Asgarde with Kotlin\n\n`Apache Beam Java` can be used with `Kotlin`, and it's make the experience more enjoyable.\n\n`Asgarde` proposes also `Kotlin` extensions to use `CollectionComposer` class with more expressive/concise code and with\nfunctional programming style.\n\n\u003e :warning: **Kotlin Asgarde is proposed from 0.15.0 and Beam 2.36.0 versions**\n\u003e :warning: **The current Kotlin version used with Asgarde is : 1.9.22**\n\nLet's take a previous example of `Asgarde Java` pipeline with error handling :\n\n```java\nfinal WithFailures.Result\u003cPCollection\u003cInteger\u003e, Failure\u003e resultComposer = CollectionComposer.of(input)\n        .apply(\"Map\", MapElements.into(TypeDescriptors.strings()).via((String word)-\u003e word + \"Test\"))\n        .apply(\"FlatMap\", FlatMapElements\n                          .into(TypeDescriptors.strings())\n                          .via((String line) -\u003e Arrays.asList(Arrays.copyOfRange(line.split(\" \"), 1, 5))))\n        .apply(\"ParDo\", MapElementFn.into(TypeDescriptors.integers()).via(word -\u003e 1/word.length()))\n        .getResult();\n```\n\nIn `Asgarde Kotlin` the same pipeline is :\n\n```kotlin\nimport fr.groupbees.asgarde.*\n\nval result: Result\u003cPCollection\u003cInt\u003e, Failure\u003e = CollectionComposer.of(words)\n    .map(\"Map\") { word -\u003e word + \"Test\" }\n    .flatMap(\"FlatMap\") { Arrays.asList(*Arrays.copyOfRange(it.split(\" \").toTypedArray(), 1, 5)) }\n    .mapFn(\"ParDo\", { word -\u003e 1 / word.length })\n    .result\n```\n\nThe Kotlin code of `Asgarde` uses `extensions`.\\\nTo use these extensions, the following `import` must be added :\n\n```kotlin\nimport fr.groupbees.asgarde.*\n```\n\nThis feature is great because we can mix native `Asgarde` code with \nfunctions dedicated to Kotlin, example : \n\n```kotlin\nval result: Result\u003cPCollection\u003cInt\u003e, Failure\u003e = CollectionComposer.of(words)\n    .apply(\"Map\", MapElements.into(TypeDescriptors.strings()).via(SerializableFunction { String word -\u003e word + \"Test\" })\n    .flatMap(\"FlatMap\") { Arrays.asList(*Arrays.copyOfRange(it.split(\" \").toTypedArray(), 1, 5)) }\n    .mapFn(\"ParDo\", { word -\u003e 1 / word.length })\n    .result\n```\n\nIn this case, the first `map` function has been replaced by native `apply` function with `MapElements`\\\nThe type of `lambda expression` in `via` function needs to be specified in `Kotlin`, because this can take `ProcessFunction` \nor `SerializableFunction`.\\\nThis causes an ambiguity (`SerializableFunction` is used in this example).\n\n\nIn the following sections, all `Asgarde` native components and their equivalents in `Kotlin` are proposed.\n\n### Extension for MapElements\n\n`Asgarde Java` :\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", MapElements.into(TypeDescriptor.of(OtherTeam.class)).via(team -\u003e TestSettings.toOtherTeam(team)))\n        .getResult();\n```\n\nWith method reference\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", MapElements.into(TypeDescriptor.of(OtherTeam.class)).via(TestSettings::toOtherTeam))\n        .getResult();\n```\n\n`Asgarde Kotlin` :\n\n```kotlin\nCollectionComposer.of(teams)\n    .map(\"Step name\") { team -\u003e TestSettings.toOtherTeam(team) }\n    .result\n```\n\nWith `Kotlin` `it`\n\n```kotlin\nCollectionComposer.of(teams)\n    .map(\"Step name\") { TestSettings.toOtherTeam(it) }\n    .result\n```\n\nThe `Kotlin` version takes only a lambda type in the inline function, \n`Kotlin` recommends writing the lambda in a separated parenthesis.\n\n### Extension for FlatMapElements\n\n`Asgarde Java` :\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", FlatMapElements.into(of(Player.class)).via(team -\u003e team.getPlayers()))\n        .getResult();\n```\n\nWith method reference\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", FlatMapElements.into(of(Player.class)).via(Team::getPlayers))\n        .getResult();\n```\n\n`Asgarde Kotlin` :\n\n```kotlin\nCollectionComposer.of(teams)\n    .flatMap(\"Step name\") { team -\u003e team.players }\n    .result\n```\n\nWith `Kotlin` `it`\n\n```kotlin\nCollectionComposer.of(teams)\n    .flatMap(\"Step name\") { it.players }\n    .result\n```\n\n### Extension for MapElement with failure\n\n`Asgarde Java` :\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", MapElements\n                            .into(of(Team.class))\n                            .via(team -\u003e toTeamWithPsgError(team))\n                            .exceptionsInto(of(Failure.class))\n                            .exceptionsVia(exElt -\u003e Failure.from(\"Step name\", exElt)))\n        .getResult();\n```\n\nWith method reference\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", MapElements\n                            .into(of(Team.class))\n                            .via(this::toTeamWithPsgError)\n                            .exceptionsInto(of(Failure.class))\n                            .exceptionsVia(exElt -\u003e Failure.from(\"Step name\", exElt)))\n        .getResult();\n```\n\n`Asgarde Kotlin` :\n\n```kotlin\nCollectionComposer.of(teamCollection)\n    .mapWithFailure(\n      \"Step name\",\n      { team -\u003e toTeamWithPsgError(team) },\n      { exElt -\u003e Failure.from(\"Step name\", exElt) }\n    )\n```\n\nWith `Kotlin` `it`\n\n```kotlin\nCollectionComposer.of(teamCollection)\n    .mapWithFailure(\n      \"Step name\",\n      { toTeamWithPsgError(it) },\n      { Failure.from(\"Step name\", it) }\n    )\n```\n\n### Extension for FlatMapElement with failure\n\n`Asgarde Java` :\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", FlatMapElements\n                            .into(of(Player.class))\n                            .via(team -\u003e simulateFlatMapErrorPsgTeam(team))\n                            .exceptionsInto(of(Failure.class))\n                            .exceptionsVia(exElt -\u003e Failure.from(\"Step name\", exElt)))\n        .getResult();\n```\n\nWith method reference\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", FlatMapElements\n                            .into(of(Player.class))\n                            .via(this::simulateFlatMapErrorPsgTeam)\n                            .exceptionsInto(of(Failure.class))\n                            .exceptionsVia(exElt -\u003e Failure.from(\"Step name\", exElt)))\n        .getResult();\n```\n\n`Asgarde Kotlin` :\n\n```kotlin\nCollectionComposer.of(teamCollection)\n    .flatMapWithFailure(\n      \"Step name\",\n      { team -\u003e simulateFlatMapErrorPsgTeam(team) },\n      { exElt -\u003e Failure.from(\"Step name\", exElt) }\n    )\n    .result\n```\n\nWith `Kotlin` `it`\n\n```kotlin\nCollectionComposer.of(teamCollection)\n    .flatMapWithFailure(\n      \"Step name\",\n      { simulateFlatMapErrorPsgTeam(it) },\n      { Failure.from(\"Step name\", it) }\n    )\n    .result\n```\n\n\n### Extension for MapElementFn\n\n`Asgarde Java` :\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", MapElementFn\n                              .into(of(OtherTeam.class))\n                              .via(team -\u003e TestSettings.toOtherTeam(team))\n                              .withSetupAction(() -\u003e System.out.print(\"Test\"))\n                              .withStartBundleAction(() -\u003e System.out.print(\"Test\"))\n                              .withFinishBundleAction(() -\u003e System.out.print(\"Test\"))\n                              .withTeardownAction(() -\u003e System.out.print(\"Test\")))\n        .getResult();\n```\n\nWith method reference\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", MapElementFn\n                                  .into(of(OtherTeam.class))\n                                  .via(TestSettings::toOtherTeam)\n                                  .withSetupAction(() -\u003e System.out.print(\"Test\"))\n                                  .withStartBundleAction(() -\u003e System.out.print(\"Test\"))\n                                  .withFinishBundleAction(() -\u003e System.out.print(\"Test\"))\n                                  .withTeardownAction(() -\u003e System.out.print(\"Test\")))\n        .getResult();\n```\n\n`Asgarde Kotlin` :\n\n```kotlin\nCollectionComposer.of(teams)\n      .mapFn(\"Step name\",\n        { team -\u003e TestSettings.toOtherTeam(team) },\n        setupAction = { print(\"Test\") }, \n        startBundleAction = { print(\"Test\") }, \n        finishBundleAction = { print(\"Test\") }, \n        teardownAction = { print(\"Test\") }\n      )\n      .result\n```\n\nWith `Kotlin` `it`\n\n```kotlin\nCollectionComposer.of(teams)\n    .mapFn(\"Step name\",\n      { TestSettings.toOtherTeam(it) },\n      setupAction = { print(\"Test\") }, \n      startBundleAction = { print(\"Test\") }, \n      finishBundleAction = { print(\"Test\") },\n      teardownAction = { print(\"Test\") }\n    )\n    .result\n```\n\n### Extension for FlatMapElementFn\n\n`Asgarde Java` :\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", FlatMapElementFn\n                              .into(of(Player.class))\n                              .via(team -\u003e team.getPlayers())\n                              .withSetupAction(() -\u003e System.out.print(\"Test\"))\n                              .withStartBundleAction(() -\u003e System.out.print(\"Test\"))\n                              .withFinishBundleAction(() -\u003e System.out.print(\"Test\"))\n                              .withTeardownAction(() -\u003e System.out.print(\"Test\")))\n        .getResult();\n```\n\nWith method reference\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", FlatMapElementFn\n                                  .into(of(Player.class))\n                                  .via(Team::getPlayers)\n                                  .withSetupAction(() -\u003e System.out.print(\"Test\"))\n                                  .withStartBundleAction(() -\u003e System.out.print(\"Test\"))\n                                  .withFinishBundleAction(() -\u003e System.out.print(\"Test\"))\n                                  .withTeardownAction(() -\u003e System.out.print(\"Test\")))\n        .getResult();\n```\n\n`Asgarde Kotlin` :\n\n```kotlin\nCollectionComposer.of(teams)\n      .flatMapFn(\"Step name\",\n        { team -\u003e team.players },\n        setupAction = { print(\"Test\") }, \n        startBundleAction = { print(\"Test\") }, \n        finishBundleAction = { print(\"Test\") }, \n        teardownAction = { print(\"Test\") }\n      )\n      .result\n```\n\nWith `Kotlin` `it`\n\n```kotlin\nCollectionComposer.of(teams)\n    .flatMapFn(\"Step name\",\n      { it.players },\n      setupAction = { print(\"Test\") },\n      startBundleAction = { print(\"Test\") },\n      finishBundleAction = { print(\"Test\") },\n      teardownAction = { print(\"Test\") }\n    )\n    .result\n```\n\n### Extension for MapProcessContextFn\n\n`Asgarde Java` :\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", MapProcessContextFn\n                              .from(Team.class)\n                              .into(of(OtherTeam.class))\n                              .via(ctx -\u003e TestSettings.toOtherTeam(ctx))\n                              .withSetupAction(() -\u003e System.out.print(\"Test\"))\n                              .withStartBundleAction(() -\u003e System.out.print(\"Test\"))\n                              .withFinishBundleAction(() -\u003e System.out.print(\"Test\"))\n                              .withTeardownAction(() -\u003e System.out.print(\"Test\")))\n        .getResult();\n```\n\nWith method reference\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", MapProcessContextFn\n                            .from(Team.class)\n                            .into(of(OtherTeam.class))\n                            .via(TestSettings::toOtherTeam)\n                            .withSetupAction(() -\u003e System.out.print(\"Test\"))\n                            .withStartBundleAction(() -\u003e System.out.print(\"Test\"))\n                            .withFinishBundleAction(() -\u003e System.out.print(\"Test\"))\n                            .withTeardownAction(() -\u003e System.out.print(\"Test\")))\n        .getResult();\n```\n\n`Asgarde Kotlin` :\n\n```kotlin\nCollectionComposer.of(teams)\n    .mapFnWithContext(\"Step name\",\n      { ctx: DoFn\u003cTeam, OtherTeam\u003e.ProcessContext -\u003e TestSettings.toOtherTeam(ctx) },\n      setupAction =  { print(\"Test\") }, \n      startBundleAction =  { print(\"Test\") }, \n      finishBundleAction =  { print(\"Test\") },\n      teardownAction =  { print(\"Test\") }\n    )\n    .result\n```\n\nWith `Kotlin` `it`\n\n```kotlin\nCollectionComposer.of(teams)\n    .mapFnWithContext\u003cTeam, OtherTeam\u003e(\"Step name\",\n      { TestSettings.toOtherTeam(it) },\n      setupAction =  { print(\"Test\") },\n      startBundleAction =  { print(\"Test\") },\n      finishBundleAction =  { print(\"Test\") },\n      teardownAction =  { print(\"Test\") }\n    )\n    .result\n```\n\n### Extension for FlatMapProcessContextFn\n\n`Asgarde Java` :\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", FlatMapProcessContextFn\n                              .from(Team.class)\n                              .into(of(Player.class))\n                              .via(ctx -\u003e TestSettins.toPlayers(ctx))\n                              .withSetupAction(() -\u003e System.out.print(\"Test\"))\n                              .withStartBundleAction(() -\u003e System.out.print(\"Test\"))\n                              .withFinishBundleAction(() -\u003e System.out.print(\"Test\"))\n                              .withTeardownAction(() -\u003e System.out.print(\"Test\")))\n        .getResult();\n```\n\nWith method reference\n\n```java\nCollectionComposer.of(teams)\n        .apply(\"Step name\", FlatMapProcessContextFn\n                             .from(Team.class)\n                             .into(of(Player.class))\n                             .via(TestSettins::toPlayers)\n                             .withSetupAction(() -\u003e System.out.print(\"Test\"))\n                             .withStartBundleAction(() -\u003e System.out.print(\"Test\"))\n                             .withFinishBundleAction(() -\u003e System.out.print(\"Test\"))\n                             .withTeardownAction(() -\u003e System.out.print(\"Test\")))\n        .getResult();\n```\n\n`Asgarde Kotlin` :\n\n```kotlin\nCollectionComposer.of(teams)\n    .flatMapFnWithContext(\"Step name\",\n      { ctx: DoFn\u003cTeam, Player\u003e.ProcessContext -\u003e TestSettings.toPlayers(ctx) },\n      setupAction =  { print(\"Test\") }, \n      startBundleAction =  { print(\"Test\") },\n      finishBundleAction =  { print(\"Test\") }, \n      teardownAction =  { print(\"Test\") }\n    )\n    .result\n```\n\nWith `Kotlin` `it`\n\n```kotlin\nCollectionComposer.of(teams)\n    .flatMapFnWithContext\u003cTeam, Player\u003e(\"Step name\",\n      { TestSettings.toPlayers(it) },\n      setupAction =  { print(\"Test\") },\n      startBundleAction =  { print(\"Test\") },\n      finishBundleAction =  { print(\"Test\") },\n      teardownAction =  { print(\"Test\") }\n    )\n    .result\n```\n\n### Extension for FilterFn\n\n`Asgarde Java` :\n\n```java\n CollectionComposer.of(teamCollection)\n        .apply(\"Step name\", FilterFn.by(team -\u003e simulateFilterErrorPsgTeam(team)))\n        .getResult();\n```\n\nWith method reference\n\n```java\n CollectionComposer.of(teamCollection)\n        .apply(\"Step name\", FilterFn.by(this::simulateFilterErrorPsgTeam))\n        .getResult();\n```\n\n`Asgarde Kotlin` :\n\n```kotlin\nCollectionComposer.of(teamCollection)\n    .filter(\"Step name\") { team -\u003e simulateFilterErrorPsgTeam(team) }\n    .result\n```\n\nWith `Kotlin` `it`\n\n```kotlin\nCollectionComposer.of(teamCollection)\n    .filter(\"Step name\") { simulateFilterErrorPsgTeam(it) }\n    .result\n```\n\n## Possible evolutions in the future\n\n- Maybe allow injecting a custom `Failure` object and error handling function to be used in all `apply` calls.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftosun-si%2Fasgarde","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftosun-si%2Fasgarde","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftosun-si%2Fasgarde/lists"}