Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sphinx documentation for 0.8 #339

Merged
merged 16 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

![CI build](https://github.com/scalalandio/chimney/workflows/CI%20build/badge.svg)
[![codecov.io](http://codecov.io/github/scalalandio/chimney/coverage.svg?branch=master)](http://codecov.io/github/scalalandio/chimney?branch=master)
[![License](http://img.shields.io/:license-Apache%202-green.svg)](http://www.apache.org/licenses/LICENSE-2.0.txt) [![Join the chat at https://gitter.im/scalalandio/chimney](https://badges.gitter.im/scalalandio/chimney.svg)](https://gitter.im/scalalandio/chimney?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![License](http://img.shields.io/:license-Apache%202-green.svg)](http://www.apache.org/licenses/LICENSE-2.0.txt) [![Join the discussions at https://github.com/scalalandio/chimney/discussions](https://img.shields.io/github/discussions/scalalandio/chimney
)](https://github.com/scalalandio/chimney/discussions)

[![Documentation Status](https://readthedocs.org/projects/chimney/badge/?version=latest)](https://chimney.readthedocs.io/en/latest/?badge=latest)
[![Scaladoc 2.11](https://javadoc.io/badge2/io.scalaland/chimney_2.11/scaladoc%202.11.svg)](https://javadoc.io/doc/io.scalaland/chimney_2.11)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ final class PatcherUsing[T, P, Cfg <: PatcherCfg](val obj: T, val objPatch: P) {

/** Enable printing the logs from the derivation process.
*
* @see [[https://chimney.readthedocs.io/TODO]] for more details
* @see [[https://chimney.readthedocs.io/troubleshooting/debugging-macros.html]] for more details
*
* @since 0.8.0
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ final class PatcherUsing[A, Patch, Cfg <: PatcherCfg](val obj: A, val objPatch:

/** Enable printing the logs from the derivation process.
*
* @see [[https://chimney.readthedocs.io/TODO]] for more details
* @see [[https://chimney.readthedocs.io/troubleshooting/debugging-macros.html]] for more details
*
* @since 0.8.0
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ private[dsl] trait FlagsDsl[UpdateFlag[_ <: TransformerFlags], Flags <: Transfor

/** Enable printing the logs from the derivation process.
*
* @see [[https://chimney.readthedocs.io/TODO]] for more details
* @see [[https://chimney.readthedocs.io/troubleshooting/debugging-macros.html]] for more details
*
* @since 0.8.0
*/
Expand All @@ -144,7 +144,7 @@ private[dsl] trait FlagsDsl[UpdateFlag[_ <: TransformerFlags], Flags <: Transfor

/** Disable printing the logs from the derivation process.
*
* @see [[https://chimney.readthedocs.io/TODO]] for more details
* @see [[https://chimney.readthedocs.io/troubleshooting/debugging-macros.html]] for more details
*
* @since 0.8.0
*/
Expand Down
175 changes: 175 additions & 0 deletions docs/source/cookbook/libraries-with-smart-constructors.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
Libraries with smart constructors
=================================

Any type which uses a smart constructor (returning parsed result rather than
throwing an exception) would require partial transformer rather than total
transformer to convert.

If there is no common interface which could be summoned as implicit for
performing smart construction:

.. code-block:: scala

// assuming Scala 3 or -Xsource:3 for fixed private constructors
MateuszKubuszok marked this conversation as resolved.
Show resolved Hide resolved
// so that Username.apply and .copy would be private
final case class Username private (value: String)
object Username {
def parse(value: String): Either[String, Username] =
if (value.isEmpty) Left("Username cannot be empty")
else Right(Username(value))
}

then partial transformer would have to be created manually:

.. code-block:: scala

import io.scalaland.chimney.PartialTransformer
import io.scalaland.chimney.partial

implicit val usernameParse: PartialTransformer[String, Username] =
PartialTransformer[String, Username] { value =>
partial.Result.fromEitherString(Username.parse(value))
}

However, if there was some type class interface, e.g.

.. code-block:: scala

trait SmartConstructor[From, To] {
def parse(from: From): Either[String, To]
}

.. code-block:: scala

object Username extends SmartConstructor[String, Username] {
// ...
}

we could use it to construct ``PartialTransformer`` automatically:

.. code-block:: scala

import io.scalaland.chimney.PartialTransformer
import io.scalaland.chimney.partial

implicit def smartConstructedPartial[From, To](
implicit smartConstructor: SmartConstructor[From, To]
): PartialTransformer[From, To] =
PartialTransformer[From, To] { value =>
partial.Result.fromEitherString(smartConstructor.parse(value))
}

The same would be true about extracting values from smart constructed types
(if they are not ``AnyVal``\s, handled by Chimney out of the box).

Let's see how we could implement support for automatic transformations of
types provided in some popular libraries.

Scala NewType
-------------

`NewType <https://github.com/estatico/scala-newtype>`_ is a macro-annotation-based
library which attempts to remove runtime overhead from user's types.

.. code-block:: scala

import io.estatico.newtype.macros.newtype

@newtype case class Username(value: String)

would be rewritten to become ``String`` in the runtime, while prevent
mixing ``Username`` values with other ``String``\s accidentally.

NewType provides ``Coercible`` type class `to allow generic wrapping and unwrapping <https://github.com/estatico/scala-newtype#coercible-instance-trick>`_
of ``@newtype`` values. This type class is not able to validate
the casted type, so it safe to use only if NewType is used as a wrapper around
another type which performs this validation e.g. Refined Type.

.. code-block:: scala

import io.estatico.newtype.Coercible
import io.scalaland.chimney.Transformer

implicit def newTypeTransformer[From, To](
implicit coercible: Coercible[From, To]
): Transformer[From, To] = coercible(_)

Monix Newtypes
--------------

`Monix's Newtypes <https://newtypes.monix.io/>`_ is similar to NewType in that
it tries to remove wrapping in runtime. However, it uses different tricks
(and syntax) to achieve it.

.. code-block:: scala

import monix.newtypes._

type Username = Username.Type
object Username extends NewtypeValidated[String] {
def apply(value: String): Either[BuildFailure[Type], Type] =
if (value.isEmpty)
Left(BuildFailure("Username cannot be empty"))
else
Right(unsafeCoerce(value))
}

Additionally, it provides 2 type classes: one to extract value
(``HasExtractor``) and one to wrap it (possibly validating, ``HasBuilder``).
We can use them to provide unwrapping ``Transformer`` and wrapping
``PartialTransformer``:

.. code-block:: scala

import io.scalaland.chimney.{PartialTransformer, Transformer}
import io.scalaland.chimney.partial
import monix.newtypes._

implicit def unwrapNewType[Outer, Inner](
implicit extractor: HasExtractor.Aux[Outer, Inner]
): Transformer[Outer, Inner] = extractor.extract(_)

implicit def wrapNewType[Inner, Outer](
implicit builder: HasBuilder.Aux[Inner, Outer]
): PartialTransformer[Inner, Outer] = PartialTransformer[Inner, Outer] { value =>
partial.Result.fromEitherString(
builder.build(value).left.map(_.toReadableString)
)
}

Refined Types
-------------

`Refined Types <https://github.com/fthomas/refined>`_ is a library aiming to provide automatic validation of some
popular constraints as long as we express them in value's type.

.. code-block:: scala

import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.collections._

type Username = String Refined NonEmpty

We can validate using dedicated type class (``Validate``), while extraction
is a simple accessor:

.. code-block:: scala

import eu.timepit.refined.api.{Refined, Validate}
import io.scalaland.chimney.{PartialTransformer, Transformer}
import io.scalaland.chimney.partial

implicit def extractRefined[Type, Refinement]:
Transformer[Type Refined Refinement, Type] =
_.value

implicit def validateRefined[Type, Refinement](
implicit validate: Validate.Plain[Type, Refinement]
): PartialTransformer[Type, Type Refined Refinement] =
PartialTransformer[Type, Type Refined Refinement] { value =>
partial.Result.fromOption(
validate.validate(value).fold(Some(_), _ => None)
)
}
Loading
Loading