{"id":19804059,"url":"https://github.com/mateuszkubuszok/pipez","last_synced_at":"2025-05-01T05:31:24.061Z","repository":{"id":58530729,"uuid":"512531170","full_name":"MateuszKubuszok/pipez","owner":"MateuszKubuszok","description":"Generate type mappers for your own type class","archived":false,"fork":false,"pushed_at":"2023-05-11T12:51:12.000Z","size":437,"stargazers_count":13,"open_issues_count":6,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-06T09:12:35.042Z","etag":null,"topics":["auto-mapping","bolierplate","data-rewriting","generic-programming","scala","scala-library","typeclass-derivation"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MateuszKubuszok.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":"2022-07-10T20:19:16.000Z","updated_at":"2024-08-17T12:01:59.000Z","dependencies_parsed_at":"2023-02-10T14:16:05.116Z","dependency_job_id":null,"html_url":"https://github.com/MateuszKubuszok/pipez","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MateuszKubuszok%2Fpipez","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MateuszKubuszok%2Fpipez/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MateuszKubuszok%2Fpipez/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MateuszKubuszok%2Fpipez/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MateuszKubuszok","download_url":"https://codeload.github.com/MateuszKubuszok/pipez/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251830449,"owners_count":21650802,"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","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":["auto-mapping","bolierplate","data-rewriting","generic-programming","scala","scala-library","typeclass-derivation"],"created_at":"2024-11-12T08:02:45.434Z","updated_at":"2025-05-01T05:31:24.050Z","avatar_url":"https://github.com/MateuszKubuszok.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pipez\n\n[![Pipez JVM](https://index.scala-lang.org/mateuszkubuszok/pipez/pipez/latest-by-scala-version.svg?platform=jvm)](https://search.maven.org/artifact/com.kubuszok/pipez_2.13)\n[![Pipez JS](https://index.scala-lang.org/mateuszkubuszok/pipez/pipez/latest-by-scala-version.svg?platform=sjs1)](https://search.maven.org/artifact/com.kubuszok/pipez_sjs1_2.13)\n[![Pipez Native](https://index.scala-lang.org/mateuszkubuszok/pipez/pipez/latest-by-scala-version.svg?platform=native0.4)](https://search.maven.org/artifact/com.kubuszok/pipez_native0.4_3)\n\n[![Scaladoc](https://javadoc.io/badge2/com.kubuszok/pipez_2.13/scaladoc%202.13.svg)](https://javadoc.io/doc/com.kubuszok/pipez_2.13)\n[![Scaladoc](https://javadoc.io/badge2/com.kubuszok/pipez_3/scaladoc%203.svg)](https://javadoc.io/doc/com.kubuszok/pipez_3)\n![CI build](https://github.com/MateuszKubuszok/pipez/workflows/CI%20build/badge.svg)\n[![License](http://img.shields.io/:license-Apache%202-green.svg)](http://www.apache.org/licenses/LICENSE-2.0.txt)\n\nScala library for type-safe data-transformations, which allows you to build-in Chimney-like abilities to your own type classes and effects.\n\n\u003e Pipez is a result of research about possible ways of migrating Chimney to Scala 3. It focuses on a certain **deprecated** type class from Chimney- `TransformerF` - and while it attempts to replicate as much features as possible it is **not** intended to replace Chimney nor reimplement all of its features.\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n**Table of Contents**\n\n- [Installation](#installation)\n- [Motivating example](#motivating-example)\n- [Custom parsers](#custom-parsers)\n  - [Automatic derivation](#automatic-derivation)\n  - [Semiautomatic derivation](#semiautomatic-derivation)\n  - [Configured derivation](#configured-derivation)\n- [Supported features and configuration options](#supported-features-and-configuration-options)\n  - [Case classes and Java Beans conversions](#case-classes-and-java-beans-conversions)\n    - [`addField` configuration](#addfield-configuration)\n    - [`renameField` configuration](#renamefield-configuration)\n    - [`plugInField` configuration](#pluginfield-configuration)\n    - [`fieldMatchingCaseInsensitive` configuration](#fieldmatchingcaseinsensitive-configuration)\n    - [`addFallbackToValue` configuration](#addfallbacktovalue-configuration)\n    - [`enableFallbackToDefaults` configuration](#enablefallbacktodefaults-configuration)\n    - [`recursiveDerivation` configuration](#recursivederivation-configuration)\n  - [Tuples](#tuples)\n  - [Sealed hierarchies and enums conversions](#sealed-hierarchies-and-enums-conversions)\n    - [`removeSubtype` configuration](#removesubtype-configuration)\n    - [`renameSubtype` configuration](#renamesubtype-configuration)\n    - [`plugInSubtype` configuration](#pluginsubtype-configuration)\n    - [`enumMatchingCaseInsensitive` configuration](#enummatchingcaseinsensitive-configuration)\n  - [AnyVals conversions](#anyvals-conversions)\n  - [Deriving instances for Scala 3 types in Scala 2.13 and vice-versa](#deriving-instances-for-scala-3-types-in-scala-213-and-vice-versa)\n  - [Features you have to implement yourself](#features-you-have-to-implement-yourself)\n- [How to define `PipeDerivation`](#how-to-define-pipederivation)\n  - [Enriching Context with path to value](#enriching-context-with-path-to-value)\n  - [Debugging](#debugging)\n  - [Contracts and laws](#contracts-and-laws)\n- [Pipez and Chimney](#pipez-and-chimney)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Installation\n\nCore derivation library:\n\n```scala\n// Add to sbt if you use only JVM Scala\nlibraryDependencies += \"com.kubuszok\" %% \"pipez\" % \"\u003cversion\u003e\"\n// Add to sbt it you use Scala.js or Scala Native\nlibraryDependencies += \"com.kubuszok\" %%% \"pipez\" % \"\u003cversion\u003e\"\n```\n\nData transformation DSL:\n\n```scala\n// Add to sbt if you use only JVM Scala\nlibraryDependencies += \"com.kubuszok\" %% \"pipez-dsl\" % \"\u003cversion\u003e\"\n// Add to sbt it you use Scala.js or Scala Native\nlibraryDependencies += \"com.kubuszok\" %%% \"pipez-dsl\" % \"\u003cversion\u003e\"\n```\n\n## Motivating example\n\nYour user send a request to you and, while handling it, you obtained the domain data:\n\n```scala\nenum UserType:\n  case Normal\n  case Admin\n\nfinal case class User(\n  id:       String,\n  name:     String,\n  password: Array[Byte],\n  userType: UserType\n)\n```\n\nwhich you have to manually rewrite into format used by your API endpoints:\n\n```scala\nenum UType:\n  case Normal\n  case Admin\n\nfinal case class ApiUser(\n  id:       String,\n  name:     String,\n  userType: UType\n)\n```\n\nThese types are almost identical, so rewriting it would be pretty dumb - we should be able to convert one into the other\njust by matching the corresponding fields (or subtypes) by name! That's what `Convert[From, To]` type class from pipez\nDSL does:\n\n```scala\n// given such User\nval user: User = User(\n  id       = \"user-1\",\n  name     = \"User #1\",\n  password = \"some-hash\".getBytes,\n  userType = UserType.Normal,\n)\n\nimport pipez.dsl.* // provides convertInto\n\n// we can generate the conversion into ApiUser!\nval apiUser = user.convertInto[ApiUser]\n\napiUser == ApiUser(\n  id       = \"user-1\",\n  name     = \"User #1\",\n  userType = UType.NORMAL\n)\n```\n\nUnder the hood there is a type class:\n\n```scala\n// Converts From value into To value, without failure\ntrait Converter[From, To]:\n  def convert(from: From): To\n```\n\nand `user.convertInto[ApiUser]` creates an instance of `Converter[User, ApiUser]` and calls `.convert(user)` on it. It\nbasically writes for us:\n\n```scala\n// This is (more or less) what\n//   user.convertInto[ApiUser]\n// generates:\nnew Converter[User, ApiUser] {\n\n  def convert(from: User): ApiUser = ApiUser(\n    // Here we map each User field to corresponding to ApiUser\n    // field by their name:\n    id       = from.id,\n    name     = from.name,\n    // and when we need to map UserType to UType\n    // we map them by their corresponding subtype names:\n    userType = from.userName match {\n      case UserType.Normal =\u003e UType.Normal\n      case UserType.Admin  =\u003e UType.Admin\n    }\n  )\n}\n// Finally, we apply User value to created Converter\n.convert(user)\n```\n\nletting us forget about writing all this dumb, error-prone boilerplate code ourselves!\n\n\u003e `Converter` is a demonstration how Pipez can be used to implement something similar to Chimney's `Transformer`.\n\nGreat, but what if needed to convert things the other way? We would receive the password and we would have to hash it\nwith a function:\n\n```scala\n// Left describes the parsing error\ndef hashPassword(password: String): Either[String, Array[Byte]]\n```\n\nand we receive:\n\n```scala\nfinal case class ApiUserWithPassword(\n  id:       String,\n  name:     String,\n  password: String, // different type than User.password\n  userType: UType\n)\n```\n\nThis would require some ability to fail conversion with an error. We still have a way of parsing that automatically!\n\n```scala\nimport pipez.dsl.* // provides parseFastInto and Parser\n\nval apiUserWithPassword: ApiUserWithPassword = ...\n\n// We are turning the function into Parser instance...\nimplicit val passwordParser: Parser[String, Array[Byte]] =\n  Parser.instance(hashPassword)\n\n// ...because it will be picked up when looking how to perform conversion\n//   ApiUserWithPassword.password =\u003e User.password\n// which might produce errors:\nval userResult = apiUserWithPassword.parseFastInto[User]\n\n// assuming correct password:\nuserResult == Right(User(\n  id       = apiUserWithPassword.id,\n  name     = apiUserWithPassword.name,\n  password = hashPassword(apiUserWithPassword.password).right.get,\n  userType = apiUserWithPassword.userType match {\n    case UType.Normal =\u003e UserType.Normal,\n    case UType.Admin  =\u003e UserType.Admin\n  }\n))\n```\n\nParsing allows you to calculate all possible errors or give up upon the first one - for that you have `parseFullInto`\nand `parseFastInto` methods. Similarly to `Converter` there is `Parser` type class:\n\n```scala\n// Converts From value into To value, but allows conversion to fail, report path\n// to the failed value, and chose between fail fast and full error reporting.\ntrait Parser[From, To]:\n\n  def parse(\n    from:     From, // parsed input\n    path:     Parser.Path, // Vector of fields/subtype matches leading to the value\n    failFast: Parser.ShouldFailFast // =:= Boolean, should we fail fast or continue\n  ): Parser.ParsingResult[To] // =:= Either[Errors, To], accumulates parsing errors\n\n  final def parseFast(from: From): Parser.ParsingResult[To] =\n    parse(from, Vector.empty, failFast = true)\n  final def parseFull(from: From): Parser.ParsingResult[To] =\n    parse(from, Vector.empty, failFast = false)\n```\n\nWhen we called `apiUserWithPassword.parseFastInto[User]` we created `Parser[ApiUserWithPassword, User]` instance\nand called `.parseFast(apiUserWithPassword.parseFastInto)` on it.\n\n```scala\n// This is (more or less!) what\n//   apiUserWithPassword.parseFastInto[User]\n// generates:\nnew Parser[ApiUserWithPassword, User] {\n\n  def parse(\n    from:     ApiUserWithPassword,\n    path:     Parser.Path,\n    failFast: Parser.ShouldFailFast\n  ): Parser.ParsingResult[User] =\n    // We start by calling Parsers for types we cannot just\n    // copy paste:\n    passwordParser.parse(\n      from.password,\n      // giving them some extra informaton how we got the value\n      path :+ PathSegment.AtField(\"password\"),\n      failFast\n    ).map { password =\u003e\n      // once all \"parsable\" fields are parsed, we just rewrite\n      // the rest matching fields and subtypes by their name\n      User(\n        id       = from.id,\n        name     = from.name,\n        password = password,\n        userType = from.userType match {\n          case UType.Normal =\u003e UserType.Normal,\n          case UType.Admin  =\u003e UserType.Admin\n        }\n      )\n    }\n}\n// Finally, we pass ApiUserWithPassword to try to convert it to User\n.parseFast(apiUserWithPassword)\n```\n\n\u003e `Parser` is a demonstration how Pipez can be used to implement something similar to Chimney's `TransformerF` with\n\u003e effect `F` based on `Either` and `List` of errors, with `TransformerFErrorPathSupport` provided.\n\nIn other words: `Converter` and `Parser` let us easily generate code which rewrites corresponding fields and subtypes by\nname, and plug-in our own conversion when it is not obvious how it could generate it.\n\nThere is also `PatchApplier` which is used to apply patches:\n\n```scala\ncase class Input(a: Int, b: String, c: Long)\ncase class InputPatch(c: Long)\n\nval result = Input(a = 1, b = \"b\", c = 10L).patchWith(InputPatch(c = 40L))\nresult == Input(a = 1, b = \"b\", c = 40L)\n```\n\n\u003e `PatchApplier` is a demonstration how Pipez can be used to implement something similar to Chimney's `Patcher`.\n\n## Custom parsers\n\nWhile `Converter` and `Parser` achieve quite a lot - and can do a lot more as you'll see reading this documentation -\nthe true power of Pipez comes from the ability to defining your own conversion type class and deriving its instances for\nit! How can you achieve it?\n\nPerhaps you have your own conversion type class:\n\n```scala\n// Maybe something similar to Converter (no failures, no extra arguments)?\ntrait NonFailing[From, To]:\n  def convert(from: From): To\n\n// Or something which need to pass extra arguments next to converted value?\ntrait WithContext[From, To]:\n  def convert(from: From, pathToFrom: String): To\n\n// Or something which wraps result in some result type, to handle errors or effects?\ntrait WithResultType[From, To]:\n  def convert(from: From): Either[String, To]\n\n// Or both?\ntrait WithContextAndResult[From, To]:\n  def convert(from: From, pathToFrom: String): Either[String, To]\n```\n\nthen once you define a single `PipeDerivation` implicit (I suggest putting it in the companion object):\n\n```scala\nimport pipez.*\n\n// putting an instance in companion object:\n\nobject NonFailing:\n  implicit val derivation: PipeDerivation[NonFailing] = ???\n\nobject WithContext:\n  implicit val derivation: PipeDerivation[WithContext] = ???\n\nobject WithResultType:\n  implicit val derivation: PipeDerivation[WithResultType] = ???\n\nobject WithContextAndResult:\n  implicit val derivation: PipeDerivation[WithContextAndResult] = ???\n```\n\nyou'll get access to derivation abilities! (For now let's focus what possibilities it gives us, as we can always get to\n[how to define this implicit](#how-to-define-pipederivation) later on).\n\nSo, how to access the derived instances?\n\n### Automatic derivation\n\nIf you want to be able to summon derived instance always, then **automatic derivation** is for you. You can enable it by\nmixing in `PipeAutoSupport` to your companion object:\n\n```scala\nimport pipez.*\n\n// enabling automatic derivation with companion object\n\nobject NonFailing extends PipeAutoSupport[NonFailing]:\n  implicit val derivation: PipeDerivation[NonFailing] = ???\n\nobject WithContext extends PipeAutoSupport[WithContext]:\n  implicit val derivation: PipeDerivation[WithContext] = ???\n\nobject WithResultType extends PipeAutoSupport[WithResultType]:\n  implicit val derivation: PipeDerivation[WithResultType] = ???\n\nobject WithContextAndResult extends PipeAutoSupport[WithContextAndResult]:\n  implicit val derivation: PipeDerivation[WithContextAndResult] = ???\n```\n\nThis will let you `summon[NonFailing[User, ApiUser]]` without any additional imports\n\n```scala\n// PipeDerivation instance with auto derivation unlocks summoning:\nsummon[NonFailing[User, ApiUser]]\nsummon[WithContext[User, ApiUser]]\nsummon[WithResultType[User, ApiUser]]\nsummon[WithContextAndResult[User, ApiUser]]\n```\n\nwhich let you define a DSL fetchig such instance (like was done with `Converter` and `Parser` in Pipez DSL).\n\n### Semiautomatic derivation\n\nSometimes pulling type class instances out of thin air without user doing anything, might be a bit dangerous. You might\nprefer them to define them somewhere explicitly - but not necessarily writing them by hand! This is when semiautomatic\nderivation comes handy:\n\n```scala\nimport pipez.*\n\n// enabling semi-automatic derivation with companion object\n\nobject NonFailing extends PipeSemiautoSupport[NonFailing]:\n  implicit val derivation: PipeDerivation[NonFailing] = ...\n\nobject WithContext extends PipeSemiautoSupport[WithContext]:\n  implicit val derivation: PipeDerivation[WithContext] = ...\n\nobject WithResultType extends PipeSemiautoSupport[WithResultType]:\n  implicit val derivation: PipeDerivation[WithResultType] = ...\n\nobject WithContextAndResult extends PipeSemiautoSupport[WithContextAndResult]:\n  implicit val derivation: PipeDerivation[WithContextAndResult] = ...\n```\n\nThis will let your users create dumb instances with a one-liner:\n\n```scala\n// CompanionObject.derive[From, To] generates an instance\n\nimplicit val nonFailingUser =\n  NonFailing.derive[User, ApiUser]\nimplicit val withContextUser =\n  WithContext.derive[User, ApiUser]\nimplicit val withResultTypeUser =\n  WithResultType.derive[User, ApiUser]\nimplicit val withContextAndResultUser =\n  WithContextAndResult.derive[User, ApiUser]\n```\n\nmaking it explicit that the conversion is defined in one place, but not forcing user to write all the dumb code by hand.\n\n### Configured derivation\n\nNot all types will be so nice to have corresponding fields or subtypes names. Sometimes a new field will appear in\nthe output type, some field or type will be renamed or maybe you'll want to plug-in your own conversion for a particular pairs of fields.\n\nThis requires an additional API and `PipeSemiautoConfiguredSupport` has you covered:\n\n```scala\n// With that CompanionObject.derive[From, To] will use defaults while\n// CompanionObject.derive(CompanionObject.Config[From, To]) will let us\n// pass custom configuration\n\nobject NonFailing\n    extends PipeSemiautoSupport[NonFailing]\n    with PipeSemiautoConfiguredSupport[NonFailing]:\n  implicit val derivation: PipeDerivation[NonFailing] = ...\n\nobject WithContext\n    extends PipeSemiautoSupport[WithContext]\n    with PipeSemiautoConfiguredSupport[WithContext]:\n  implicit val derivation: PipeDerivation[WithContext] = ...\n\nobject WithResultType\n    extends PipeSemiautoSupport[WithResultType]\n    with PipeSemiautoConfiguredSupport[WithResultType]:\n  implicit val derivation: PipeDerivation[WithResultType] = ...\n\nobject WithContextAndResult\n    extends PipeSemiautoSupport[WithContextAndResult]\n    with PipeSemiautoConfiguredSupport[WithContextAndResult]:\n  implicit val derivation: PipeDerivation[WithContextAndResult] = ...\n```\n\nThis allow us to pass custom configuration to derivation:\n\n```scala\n// User has password field which is missing from ApiUser so we have\n// to give the derivation a hint how to came up with the value for it\nNonFailing.derive(\n  NonFailing.Config[ApiUser, User]\n    .addField(_.password, (apiUser: ApiUser) =\u003e \"mock-password\".getBytes)\n)\n```\n\nConfiguration is build with fluent API within `.derive(...)`. This let us erase whole `Config` and leave only\nthe generated type class value. Possible configuration options will be described next to each feature which uses them:\n\n## Supported features and configuration options\n\nThe automatically generated mappings can be roughly divided into the following categories:\n\n* case classes and Java Beans\n* tuples\n* sealed hierarchies and enums\n* value types\n\n\u003e Examples below will just show semiautomatic and configured derivation, since however you use them in DSL is up to you.\n\n### Case classes and Java Beans conversions\n\n**Rules of derivation**:\n\n* `case class` has a public constructor and each of its arguments is a `val`\n* Java Bean has a public default constructor and getters/setters that you can use to access/set its values (getters have\n  `get`/`is` prefix while setters have `set` prefix)\n* both input and output is either case class or Java Bean\n* unless configuration tells otherwise each output field will require a matching input field to copy value from.\n  Matching is done by comparing names of fields (`get`/`is`/`set` prefixes are stripped). The last configuration for\n  the output field \"wins\" and tells where to get the value from\n* if value cannot be copied because the types differ, derivation will attempt to summon\n  `TypeClass[InputField, OutputField]` to convert it\n* if derivation cannot figure out where to get the value from (mismatching types + no conversion, no corresponding\n  source field), it fails\n\n```scala\n// This requires just rewriting fields in a dumb way...\nfinal case class Input(a: Int, b: String, c: Long)\nfinal case class Output(a: Int, b: String, c: Long)\n\n// ...this (Java Beans) as well\nfinal case class InputBean private (\n  @BeanProperty var a: Int,    // in 2.13 creates getA and setA\n  @BeanProperty var b: String, // in 2.13 creates getB and setB\n  @BeanProperty var c: Long    // in 2.13 creates getC and setC\n) { def this() = this(0, \"\", 0L) }\nfinal case class OutputBean private (\n  @BeanProperty var a: Int,\n  @BeanProperty var b: String,\n  @BeanProperty var c: Long\n) { def this() = this(0, \"\", 0L) }\n\n// Pipez DSL\nConverter.derive[Input, Output]\nConverter.derive[InputBean, Output]\nConverter.derive[Input, OutputBean]\nConverter.derive[InputBean, OutputBean]\nParser.derive[Input, Output]\n// your own types\nNonFailing.derive[Input, Output]\nWithContext.derive[Input, Output]\nWithResultType.derive[Input, Output]\nWithContextAndResult.derive[Input, Output]\n\n// This requires some knowledge how to turn Long to String...\nfinal case class Output2(a: Int, b: String, c: String)\n// ... without these ...\nimplicit val convertLong2Str: Converter[Long, String] = ...\nimplicit val parseLong2Str: Parser[Long, String] = ...\n\nimplicit val notFailingLong2Str: NonFailing[Long, String] = ...\nimplicit val withContextLong2Str: WithContext[Long, String] = ...\nimplicit val withResultTypeLong2Str: WithResultType[Long, String] = ...\nimplicit val withContextAndResultLong2Str: WithContextAndResult[Long, String] = ...\n// ...these would not compile:\n\n// Pipez DSL\nConverter.derive[Input, Output2]\nParser.derive[Input, Output2]\n// your own types\nNonFailing.derive[Input, Output2]\nWithContext.derive[Input, Output2]\nWithResultType.derive[Input, Output2]\nWithContextAndResult.derive[Input, Output2]\n```\n\n#### `addField` configuration\n\nTells derivation to use `TypeClass[In, OutField]` to populate the output field that might not have a corresponding input\nfield. You might use Single Abstract Method syntax:\n\n```scala\n// Output.x doesn't have a corresponding source\nfinal case class Input(a: Int, b: String, c: Long)\nfinal case class Output(a: Int, b: String, c: Long, x: Double)\n\n// Pipez DSL\nConversion.derive(\n  Conversion.Config[Input, Output]\n    .addField(_.x, (in: Input) =\u003e in.a.toDouble)\n)\nParser.derive(\n  Parser.Config[Input, Output]\n    .addField(_.x, (in: Input) =\u003e Right(in.a.toDouble))\n)\n// your own types\nNonFailing.derive(\n  NonFailing.Config[Input, Output]\n    .addField(_.x, (in: Input) =\u003e in.a.toDouble)\n)\nWithResultType.derive(\n  WithResultType.Config[Input, Output]\n    .addField(_.x, (in: Input) =\u003e Right(in.a.toDouble))\n)\n```\n\n#### `renameField` configuration\n\nTells derivation that a specific target field should use the value from a specific input field. If types differ then the\nderivation will attempt to summon a type class to convert it:\n\n```scala\nfinal case class Input(a: Int, b: String, c: Long, x: Double)\nfinal case class Output(a: Int, b: String, c: Long, y: Double)\n\n// Pipez DSL\nConversion.derive(\n  Conversion.Config[Input, Output]\n    .renameField(_.x, _.y)\n)\nParser.derive(\n  Parser.Config[Input, Output]\n    .renameField(_.x, _.y)\n)\n// your own types\nNonFailing.derive(\n  NonFailing.Config[Input, Output]\n    .renameField(_.x, _y)\n)\nWithResultType.derive(\n  WithResultType.Config[Input, Output]\n    .renameField(_.x, _y)\n)\n```\n\n#### `plugInField` configuration\n\nTells derivation to use a specific, manually provided type class instance to convert one field to another:\n\n```scala\nfinal case class Input(a: Int, b: String, c: Long, x: Float)\nfinal case class Output(a: Int, b: String, c: Long, y: Double)\n\n// Pipez DSL\nConversion.derive(\n  Conversion.Config[Input, Output]\n    .plugInField(_.x, _.y, (in: Float) =\u003e in.toDouble)\n)\nParser.derive(\n  Parser.Config[Input, Output]\n    .plugInField(_.x, _.y, (in: Float) =\u003e Right(in.toDouble))\n)\n// your own types\nNonFailing.derive(\n  NonFailing.Config[Input, Output]\n    .plugInField(_.x, _y, (in: Float) =\u003e in.toDouble)\n)\nWithResultType.derive(\n  WithResultType.Config[Input, Output]\n    .plugInField(_.x, _y, (in: Float) =\u003e Right(in.toDouble))\n)\n```\n\n#### `fieldMatchingCaseInsensitive` configuration\n\nBy default, field matching is case-sensitive. This flag enables case-insensitive comparison:\n\n```scala\n// This would fail as fields have different cases\n// with with case-insensitive matching it works\nfinal case class Input(a: Int, b: String, c: Long)\nfinal case class Output(A: Int, B: String, C: Long)\n\n// Pipez DSL\nConverter.derive(\n  Converter.Config[Input, Output]\n    .fieldMatchingCaseInsensitive\n)\nParser.derive(\n  Parser.Config[Input, Output]\n    .fieldMatchingCaseInsensitive\n)\n// your own types\nNonFailing.derive(\n  NonFailing.Config[Input, Output]\n    .fieldMatchingCaseInsensitive\n)\nWithContext.derive(\n  WithContext.Config[Input, Output]\n    .fieldMatchingCaseInsensitive\n)\nWithResultType.derive(\n  WithResultType.Config[Input, Output]\n    .fieldMatchingCaseInsensitive\n)\nWithContextAndResult.derive(\n  WithContextAndResult.Config[Input, Output]\n    .fieldMatchingCaseInsensitive\n)\n```\n\n#### `addFallbackToValue` configuration\n\nTells derivation that if there is no field in input with some name, it should try to get this field from the value\npassed into the configuration. There can be multiple fallback values - derivation will fallback in the in order in which\nthey were provided.\n\n```scala\n// Input doesn't define x, but Fallback does\nfinal case class Input(a: Int, b: String, c: Long)\nfinal case class Output(a: Int, b: String, c: Long, x: Double)\nfinal case class Fallback(x: Double)\n\n// Pipez DSL\nConverter.derive(\n  Converter.Config[Input, Output]\n    .addFallbackToValue(Fallback(x = 10.0))\n)\nParser.derive(\n  Parser.Config[Input, Output]\n    .addFallbackToValue(Fallback(x = 10.0))\n)\n// your own types\nNonFailing.derive(\n  NonFailing.Config[Input, Output]\n    .addFallbackToValue(Fallback(x = 10.0))\n)\nWithContext.derive(\n  WithContext.Config[Input, Output]\n    .addFallbackToValue(Fallback(x = 10.0))\n)\nWithResultType.derive(\n  WithResultType.Config[Input, Output]\n    .addFallbackToValue(Fallback(x = 10.0))\n)\nWithContextAndResult.derive(\n  WithContextAndResult.Config[Input, Output]\n    .addFallbackToValue(Fallback(x = 10.0))\n)\n```\n\n#### `enableFallbackToDefaults` configuration\n\nTells derivation to use the default value if present and no other way of computing the field value is accessible:\n\n```scala\n// Conversion from Input to Output would fail since there is\n// no x in Input, but we can tell derivation to use defaults\nfinal case class Input(a: Int, b: String, c: Long)\nfinal case class Output(a: Int, b: String, c: Long, x: Double = 1.0)\n\n// Pipez DSL\nConverter.derive(\n  Converter.Config[Input, Output]\n    .enableFallbackToDefaults\n)\nParser.derive(\n  Parser.Config[Input, Output]\n    .enableFallbackToDefaults\n)\n// your own types\nNonFailing.derive(\n  NonFailing.Config[Input, Output]\n    .enableFallbackToDefaults\n)\nWithContext.derive(\n  WithContext.Config[Input, Output]\n    .enableFallbackToDefaults\n)\nWithResultType.derive(\n  WithResultType.Config[Input, Output]\n    .enableFallbackToDefaults\n)\nWithContextAndResult.derive(\n  WithContextAndResult.Config[Input, Output]\n    .enableFallbackToDefaults\n)\n```\n\nIf `addFallbackToValue` is used, derivation will fallback to defaults only after there won't be any source provided\nwith config, available in source nor available in fallback values.\n\n#### `recursiveDerivation` configuration\n\nTells derivation to allow recursive derivation for fields conversion if no implicit is present and types don't match.\nNot needed if you mixed-in `PipeAutoSupport` into your companion:\n\n```scala\n// With semi-auto this would require deriving Input2 -\u003e Output2,\n// making it implicit val and then deriving Input -\u003e Output\nfinal case class Input2(d: Double)\nfinal case class Input(a: Int, b: String, c: Long, d: Input2)\nfinal case class Output2(d: Double)\nfinal case class Output(a: Int, b: String, c: Long, d: Output2)\n\n// Pipez DSL has automatic derivation enabled\n\n// your own types (assuming they use semiauto)\nNonFailing.derive(\n  NonFailing.Config[Input, Output]\n    .recursiveDerivation\n)\nWithContext.derive(\n  WithContext.Config[Input, Output]\n    .recursiveDerivation\n)\nWithResultType.derive(\n  WithResultType.Config[Input, Output]\n    .recursiveDerivation\n)\nWithContextAndResult.derive(\n  WithContextAndResult.Config[Input, Output]\n    .recursiveDerivation\n)\n```\n\n### Tuples\n\n**Rules of derivation**:\n\n* either input or output type is a tuple\n* the other type might be a case class instead of tuple\n* fields are matched by their position (if it's a case class we consider in which order fields were defined).\n  Configuration options from case classes apply\n* if value cannot be copied because the types differ, derivation will attempt to summon\n  `TypeClass[InputField, OutputField]` to convert it\n* if derivation cannot figure out where to get the value from (mismatching types + no conversion, no corresponding\n  source field), it fails\n\n```scala\n// When derived against tuple, derivation will use position\n// of value in a case class:\nfinal case class Input(a: Int, b: String, c: Long)\nfinal case class Output(a: Int, b: String, c: Long)\n\n// If types in the same position differ, conversion will be summoned\nimplicit val convertLong2Str: Converter[Long, String] = ...\nimplicit val parseLong2Str: Parser[Long, String] = ...\n\n// Pipez DSL\nConverter.derive[Input, (Int, String, Long)]\nConverter.derive[(Int, String, Long), Output]\nConverter.derive[(Int, String, Long), (Int, String, String)] // use convertLong2Str\nParser.derive[Input, (Int, String, Long)]\nParser.derive[(Int, String, Long), Output]\nParser.derive[(Int, String, Long), (Int, String, String)] // use parseLong2Str\n// your own types\nNonFailing.derive[Input, (Int, String, Long)]\nNonFailing.derive[(Int, String, Long), Output]\n...\n```\n\n### Sealed hierarchies and enums conversions\n\n**Rules of derivation**:\n\n* both input and output is a `sealed` type or `enum`\n* subtypes will be matches by their names\n* unless configuration tells otherwise each input subtype will require a matching output subtype to target a conversion.\n  Matching is done by comparing names of subtypes. The last configuration for input subtype \"wins\" and tells where to\n  get the value from\n* by default for each `InputSubtype`, `OutputSubtype` pair derivation will attempt to summon implicit - if it cannot do\n  it, it will attempt to derive it\n* if derivation cannot figure out where to convert the subtype into (mismatching types + no conversion, no corresponding\n  target subtype), it fails\n\n```scala\n// Derivation for ADTs uses subtype/element name for matching\nsealed trait Input[+T] extends Product with Serializable:\nobject Input:\n  case object A extends Input[Nothing]\n  final case class B(b: T) extends Input[T]\n  final case class C(s: String) extends Input[String]\nenum Output[+T]:\n  case A\n  case B(b: T)\n  case C(s: String) extends Output[String]\n\n// Types' names match, no conversion needed - work OOTB!\n\n// Pipez DSL\nConverter.derive[Input, Output]\nParser.derive[Input, Output]\n// your own types\nNonFailing.derive[Input, Output]\nWithContext.derive[Input, Output]\nWithResultType.derive[Input, Output]\nWithContextAndResult.derive[Input, Output]\n```\n\n#### `removeSubtype` configuration\n\nTells derivation that for particular input subtype it should use a specified type class instance:\n\n```scala\n// C doesn't have a corresponding target\nenum Input:\n  case A\n  case B(b: Int)\n  case C(c: Int)\nenum Output:\n  case A\n  case B(b: Int)\n\n// Pipez DSL\nConversion.derive(\n  Conversion.Config[Input, Output]\n    .removeSubtype[Input.C]((in: Input.C) =\u003e Output.B(in.c))\n)\nParser.derive(\n  Parser.Config[Input, Output]\n    .removeSubtype[Input.C]((in: Input.C) =\u003e Right(Output.B(in.c)))\n)\n// your own types\nNonFailing.derive(\n  NonFailing.Config[Input, Output]\n    .removeSubtype[Input.C]((in: Input.C) =\u003e Output.B(in.c))\n)\nWithResultType.derive(\n  WithResultType.Config[Input, Output]\n    .removeSubtype[Input.C]((in: Input.C) =\u003e Right(Output.B(in.c)))\n)\n```\n\n#### `renameSubtype` configuration\n\nTells derivation that a particular input subtype it should be converted into particular output subtype:\n\n```scala\n// Input.B should target Output.C\nenum Input:\n  case A\n  case B(b: Int)\nenum Output:\n  case A\n  case C(b: Int)\n\n// Pipez DSL\nConversion.derive(\n  Conversion.Config[Input, Output]\n    .removeSubtype[Input.B, Output.C]\n)\nParser.derive(\n  Parser.Config[Input, Output]\n    .removeSubtype[Input.B, Output.C]\n)\n// your own types\nNonFailing.derive(\n  NonFailing.Config[Input, Output]\n    .removeSubtype[Input.B, Output.C]\n)\nWithResultType.derive(\n  WithResultType.Config[Input, Output]\n    .removeSubtype[Input.B, Output.C]\n)\n```\n\n#### `plugInSubtype` configuration\n\nTells derivation to use manually passed type class to convert one subtype into another\n\n```scala\n// Input.B should target Output.C\nenum Input:\n  case A\n  case B(b: Int)\nenum Output:\n  case A\n  case C(b: Double)\n\n// Pipez DSL\nConversion.derive(\n  Conversion.Config[Input, Output]\n    .plugInSubtype[Input.B, Output.C]((in: Input.B) =\u003e Output.C(in.b.toDouble))\n)\nParser.derive(\n  Parser.Config[Input, Output]\n    .plugInSubtype[Input.B, Output.C]((in: Input.B) =\u003e Right(Output.C(in.b.toDouble)))\n)\n// your own types\nNonFailing.derive(\n  NonFailing.Config[Input, Output]\n    .plugInSubtype[Input.B, Output.C]((in: Input.B) =\u003e Output.C(in.b.toDouble))\n)\nWithResultType.derive(\n  WithResultType.Config[Input, Output]\n    .plugInSubtype[Input.B, Output.C]((in: Input.B) =\u003e Right(Output.C(in.b.toDouble)))\n)\n```\n\n#### `enumMatchingCaseInsensitive` configuration\n\nBy default, subtype matching is case-sensitive. This flag enables case-insensitive comparison:\n\n```scala\n// Names don't match since they have different cases\nenum Input:\n  case Aaa\n  case Bbb(b: Int)\nenum Output:\n  case AAA\n  case BBB(b: Int)\n\n// Pipez DSL\nConversion.derive(\n  Conversion.Config[Input, Output]\n    .enumMatchingCaseInsensitive\n)\nParser.derive(\n  Parser.Config[Input, Output]\n    .enumMatchingCaseInsensitive\n)\n// your own types\nNonFailing.derive(\n  NonFailing.Config[Input, Output]\n    .enumMatchingCaseInsensitive\n)\nWithResultType.derive(\n  WithResultType.Config[Input, Output]\n    .enumMatchingCaseInsensitive\n)\n```\n\n### AnyVals conversions\n\n**Rules of derivation**:\n\n* either input or output has to be `AnyVal` type\n* the other type might be a primitive (`Int`, `Long`, `String`, etc)\n* derivation will unwrap value from input type (if needed) and wrap it (if needed) in output type\n* if types of (unwrapped) input and (unwrapped) output don't match, derivation fails\n\n```scala\n// Whether AnyVal is case class or not...\nclass Input(val value: String) extends AnyVal\nfinal case class Output(str: String) extends AnyVal\n\n/// ...wrapping and unwrapping works out of the box\n\n// Pipez DSL\nConverter.derive[Input, Output]\nConverter.derive[Input, String]\nConverter.derive[String, Output]\nParser.derive[Input, Output]\nParser.derive[Input, String]\nParser.derive[String, Output]\n\n// your own types\nNonFailing.derive[Input, Output]\nNonFailing.derive[Input, String]\nNonFailing.derive[String, Output]\n...\n```\n\n### Deriving instances for Scala 3 types in Scala 2.13 and vice-versa\n\nCross-compilation requires:\n\n* `(\"organization\" %% \"library\" % version).cross(CrossVersion.for3Use2_13)` to use Scala 2.13 type in Scala 3\n* `(\"organization\" %% \"library\" % version).cross(CrossVersion.for2_13Use3)` to use Scala 3 type in Scala 2.13, as well\n  as adding `\"-Ytasty-reader\"` flag to `scalacOptions`\n* matching versions of TASTY - Pipez was tested for 2.13.10 against 3.2.1\n\n### Features you have to implement yourself\n\n* Pipez only provides you a way of derive a type class - build-in instances of your type class you have to write\n  yourself!\n* this includes: collections, Maps, Options (lifting `F[A, B]` to `F[Option[A, B]]` or `F[A, Option[B]]` etc.)\n* Pipez doesn't automatically support: Scala Enumeration or Java enums conversion (since they can be implemented in\n  runtime)\n* Pipez isn't going to write for you some DSL which would call Pipez in a customized way\n\n## How to define `PipeDerivation`\n\nLet's say you defined you type class like this:\n\n```scala\ntrait WithContextAndResult[From, To]:\n  def convert(from: From, pathToFrom: String): Either[String, To]\n```\n\nthen you defined some typed and conversion between its fields' types:\n\n```scala\nfinal case class Input(a: Int, b: String, c: Int, x: Float)\nfinal case class Output(a: Int, b: String, c: Long, y: Double)\n\nimplicit val int2long: WithContextAndResult[Int, Long] =\n  (in, _) =\u003e Right(in.toLong)\nimplicit val float2double: WithContextAndResult[Float, Double] =\n  (in, _) =\u003e Right(in.toDouble)\n```\n\nso that you could generate the code converting it:\n\n```scala\nWithContextAndResult.derive[Input, Output]\n```\n\nhow could derivation actually create such type?\n\nWe need a few things:\n\n* sometimes we need to get the type class instance and put a field in it, so we have to know how to call it\n* this way we might end up with several results - each converting another field - which we would have to combine, so we\n  need a way of combining results\n* some of these values are not requiring conversion, and we just want to wrap them in result type\n* finally, we need something that would let us create a type class from a recipe that: takes the input (possibly with\n  these extra arguments), creates output result out of it\n* the result type and extra arguments should not leak not we shouldn't require it to be a part of the type class\n  signature\n\nHow could we express that?\n\nPipez arrived at one way of expressing these requirements using path-dependent types:\n\n```scala\n/** Pipe parameters is where you put your type class */\ntrait PipeDerivation[Pipe[_, _]] {\n\n  /** With this you will pass all extra arguments */\n  type Context\n\n  /** Type of your results */\n  type Result[Out]\n\n  /** Turns a function into your `Pipe` typeclass */\n  def lift[In, Out](f: (In, Context) =\u003e Result[Out]): Pipe[In, Out]\n\n  /** Calls `Pipe` as if it was a function */\n  def unlift[In, Out](pipe: Pipe[In, Out], in: In, ctx: Context): Result[Out]\n\n  /** Wraps raw value into `Result` */\n  def pureResult[A](a: A): Result[A]\n\n  /** Combines 2 `Results` into 1 */\n  def mergeResults[A, B, C](context: Context, ra: Result[A], rb: =\u003e Result[B], f: (A, B) =\u003e C): Result[C]\n\n  /** Useful thing but we'll talk about it later on */\n  def updateContext(context: Context, path: =\u003e Path): Context\n}\n```\n\nIf you wonder how these `Context` and `Result` could be mapped back and forth with\nyour types take a look at these examples:\n\n```scala\n// NonFailing[From, To] is equivalent to From =\u003e To\n//                                    or (From, Unit) =\u003e To\ntrait NonFailing[From, To]:\n  def convert(from: From): To\nobject NonFailing {\n  implicit val pd: PipeDerivation[NonFailing] = new PipeDerivation[NonFailing] {\n    type Context     = Unit\n    type Result[Out] = Out\n    // ...\n  }\n}\n\n// WithContext[From, To] is equivalent to (From, String) =\u003e To\ntrait WithContext[From, To]:\n  def convert(from: From, pathToFrom: String): To\nobject WithContext {\n  implicit val pd: PipeDerivation[WithContext] = new PipeDerivation[WithContext] {\n    type Context     = String\n    type Result[Out] = Out\n    // ...\n  }\n}\n\n// WithResultType[From, To] is equivalent to From =\u003e Either[String, To]\n//                                        or (From, Unit) =\u003e Either[String, To]\ntrait WithResultType[From, To]:\n  def convert(from: From): Either[String, To]\nobject WithResultType {\n  implicit val pd: PipeDerivation[WithResultType] = new PipeDerivation[WithResultType] {\n    type Context     = Unit\n    type Result[Out] = Either[String, Out]\n    // ...\n  }\n}\n\n// WithContextAndResult[From, To] is equivalent to (From, String) =\u003e Either[String, To]\ntrait WithContextAndResult[From, To]:\n  def convert(from: From, pathToFrom: String): Either[String, To]\nobject WithContextAndResult {\n  implicit val pd: PipeDerivation[WithContextAndResult] = new PipeDerivation[WithContextAndResult] {\n    type Context     = String\n    type Result[Out] = Either[String, Out]\n    // ...\n  }\n}\n```\n\nThe full implementation, for instance for `WithContextAndResult`, could look like this:\n\n```scala\nimport pipez.*\n\ntrait WithContextAndResult[From, To]:\n  def convert(from: From, pathToFrom: String): Either[String, To]\n\nobject WithContextAndResult\n    extends PipeSemiautoSupport[WithContextAndResult]\n    with PipeSemiautoConfiguredSupport[WithContextAndResult]:\n\n  implicit val pd: PipeDerivation[WithContextAndResult] =\n    new PipeDerivation[WithContextAndResult] {\n\n      /** The only extra argument is pathToFrom: String */\n      type Context = String\n\n      /** What .convert(from, path) would return */\n      type Result[Out] = Either[String, Out]\n\n      /** Create a function from a type class */\n      def lift[In, Out](\n        f: (In, String) =\u003e Either[String, Out]\n      ): WithContextAndResult[In, Out] = f(_, _) // SAM\n\n      /** Calls `Pipe` as if it was a function */\n      def unlift[In, Out](\n        converter:  WithContextAndResult[In, Out],\n        in:         In,\n        pathToFrom: String\n      ): Either[String, Out] = converter.convert(in, pathToFrom)\n\n      /** Wraps raw value into `Result` */\n      def pureResult[A](a: A): Either[String, A] = Right(a)\n\n      /** Combines 2 `Results` into 1 */\n      def mergeResults[A, B, C](\n        pathToFrom: String,\n        ra:         Either[String, A],\n        rb:         =\u003e Either[String, B],\n        f:          (A, B) =\u003e C\n      ): Either[String, C] = for {\n        a \u003c- ra\n        b \u003c- rb\n      } yield f(a, b)\n\n      /** Useful thing but we'll talk about it later on */\n      def updateContext(pathToFrom: String, path: =\u003e Path): String =\n        pathToFrom\n    }\n```\n\nThis instance simple converts between `(In, String) =\u003e Either[String, Out]` and `WithContextAndResult[In, Out]`, glues\ntogether `Either`s and wraps pure value. Nothing complex.\n\nHowever, this allow us to easily create desired  `WithContextAndResult[Input, Output]` instance. It could be done line\nthis:\n\n```scala\nWithContextAndResult.pd.lift { (in: Input, pathToFrom: String) =\u003e\n  WithContextAndResult.pd.mergeResults(\n    pathToFrom,\n    WithContextAndResult.pd.unlift(int2long, in.c, pathToFrom),\n    WithContextAndResult.pd.unlift(float2double, in.d, pathToFrom),\n    (c, d) =\u003e Output(a = in.a, b = in.b, c = c, d = d)\n  )\n}\n```\n\nSimilarly, for enum conversion, one could implement conversion for:\n\n```scala\nenum Input:\n  case A\n  case B(b: Int)\nenum Output:\n  case A\n  case B(b: Long)\n```\n\nas\n\n```scala\nWithContextAndResult.pd.lift { (in: Input, pathToFrom: String) =\u003e\n  in match {\n    case Input.A =\u003e\n      WithContextAndResult.pd.pure(Output.A)\n    case Input.B(b) =\u003e\n      WithContextAndResult.pd.mergeResult(\n        pathToFrom,\n        WithContextAndResult.pd.pure(()),\n        WithContextAndResult.pd.unlift(intoToLong, b, pathToFrom),\n        (_, b) =\u003e Output.B(b)\n      )\n  }\n}\n```\n\nWhile the *exact* ways the derivation would use the `PipeDerivation` type class is NOT a part of any contract, you can\nassume that conversion would be performed using `.map2` logic of `Applicative` (or `.parMap2` from `NonEmptyParallel`).\n\n### Enriching Context with path to value\n\nThe only not explained part of `PipeDerivation` is `updateContext`. It can be used to inject information about the path\nthat lead to the value passed through the `unlift`.\n\nBasically every time you derivation extracts field before passing it into unlift it calls `updateContext`. For case\nclasses it can look like this:\n\n```scala\nWithContextAndResult.pd.lift { (in: Input, pathToFrom: String) =\u003e\n  WithContextAndResult.pd.mergeResults(\n    pathToFrom, // not changed\n    WithContextAndResult.pd.unlift(\n      int2long,\n      in.c,\n      // we can update value of pathToFrom with knowledge that we picked .c\n      WithContextAndResult.pd.updateContext(pathToFrom, Path.root.field(\"c\"))\n    ),\n    WithContextAndResult.pd.unlift(\n      float2double,\n      in.d,\n      // we can update value of pathToFrom with knowledge that we picked .d\n      WithContextAndResult.pd.updateContext(pathToFrom, Path.root.field(\"d\"))\n    ),\n    (c, d) =\u003e Output(a = in.a, b = in.b, c = c, d = d)\n  )\n}\n```\n\nMeanwhile, for enums it can look like this:\n\n```scala\nWithContextAndResult.pd.lift { (in: Input, pathToFrom: String) =\u003e\n  in match {\n    case Input.A =\u003e\n      WithContextAndResult.pd.pure(Output.A)\n    case Input.B(b) =\u003e\n      WithContextAndResult.pd.mergeResult(\n        pathToFrom,\n        WithContextAndResult.pd.pure(()),\n        WithContextAndResult.pd.unlift(\n          intoToLong,\n          b,\n          // we can update pathToFrom with knowledge that we picked subtype B\n          WithContextAndResult.pd.updateContext(pathToFrom, Path.root.subtype(\"B\"))\n        ),\n        (_, b) =\u003e Output.B(b)\n      )\n  }\n}\n```\n\nIf we define our `updateContext` method to add `pipez.Path` value to `Context` we passed it, we will be able to have\naccess a whole path to the obtained value. With that we could e.g. create better error messages in `Left` side of\n`Either`... or log if we would make our `Result` to be side-effectful.\n\n### Debugging\n\nIf you are not sure what is happening during macro expansion and what code it generated,\npass it a configuration with `.enableDiagnosics` option.\n\n### Contracts and laws\n\nWhat Pipez promises is that:\n\n* it will not use conversion if an input field type is a subtype of output field type\n* when conversion of a field/subtype will be performed, the library will provide instance (from summoning or config)\n  and then use users code (`pipeDerivation.unlift`) to run it\n* the partial results of conversions of fields will be combined through `pipeDerivation.mergeResult`\n\nIt does not however:\n\n* provide a way of handling `Option` types (e.g. creating `F[Option[A], Option[B]]` from `F[A, B]`), or collections\n  (e.g. creating `F[Seq[A], List[B]]` from `F[A, B]`) - it is assumed that it is the responsibility of the user\n* guarantee that the results build with `PipeDerivation[F]` will be following some contracts like Cats/Cats Effect\n  laws. It is up to the user to make sure that their implementation of `PipeDerivation[F]` will not violate any laws\n\nIn other words, the user implementing their type class (function) and its derivation is responsible for defining\nthe type class contracts and its laws. Pipez is responsible to make sure that calling this type class and building\nthe final result is done through user-provided methods. With that user should be able to determine whether derived\ncode with follow the laws as well.\n\nThis is the biggest difference against `TransformerF`s from Chimney, which were coming with some predefined assumptions\nwhich made it difficult to establish what are the laws that `TransformerF` should follow, and how it could be modified\nto not break user's code.\n\n## Pipez and Chimney\n\nChimney focuses on giving user the best out-of-the-box developer experience:\n\n* it provides DSL to transform value in-place without providing any custom definitions unless necessary\n* it supports operations on _native_ types like `Option`, collections\n* it doesn't require defining custom types to make transformation possible\n* it has options like providing pure values, generating pure values from transformed object,\n* for validated transformation it can provide a path to the failed field, showing: fields, subtypes, sequence index or\n  map key or value that led to failure\n* it provides `PartialTransformer`s for the above, with fixed result type, which allows it to optimize the code\n  and provide consistent, predicatable behavior in an easy to use way\n* it deprecates `TransformerF`s in 0.7.0 for the reasons above with the intent to remove them in 0.8.0\n\nPipez on the other hand is targeting library maintainers:\n\n* it doesn't let you run it against the value, if you want it, you should configure that yourself\n* it doesn't provide any support for `Option`s, collections, maps, etc - it assumes that user can provide the necessary\n  implicits themselves\n* it only let provide user function/type class in the same shape as a way of handling added fields/removed\n  subtypes/renames\n* it is intended however to let user inject the path to the currently transformed value as some value passed next to\n  the input\n* in other words, it only focuses on generating `(In, Ctx) =\u003e Result[Out]` functions (or equivalent type classes)\n  out of implicitly acquired functions `(In2, Ctx) =\u003e Result[Out2]` mapping each field/subtype `In2` in `In`\n  to a corresponding field/subtype `Out2` in `Out` (transformations are not necessary if `In2 \u003e: Out2`)\n* how functions/type classes are combined is defined with an implicit implementation of `PipezDerivation[F]`\n* since internals of your `F` are opaque to Pipez - it works with them only through the `PipeDerivation[F]` interface -\n  many optimizations are impossible to implement, so certain overhead is unavoidable\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmateuszkubuszok%2Fpipez","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmateuszkubuszok%2Fpipez","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmateuszkubuszok%2Fpipez/lists"}