1
/*
2
 * Copyright 2016 Nicolas Rinaudo
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package kantan.codecs.shapeless
18

19
import kantan.codecs.{Decoder, Encoder}
20
import kantan.codecs.error.IsError
21
import kantan.codecs.export.{DerivedDecoder, DerivedEncoder}
22
import shapeless.{:+:, CNil, Coproduct, Generic, HList, Inl, Inr, LabelledGeneric, Lazy}
23

24
/** Provides `Codec` instances for case classes and sum types.
25
  *
26
  * The purpose of this package is to let concrete `Codec` implementations (such as kantan.csv or
27
  * kantan.regex) focus on providing instances for `HList` and `Coproduct`. Once such instances exist, this package
28
  * will take care of the transformation from and to case classes and sum types.
29
  *
30
  * Additionally, instances derived that way will be inserted with a sane precedence in the implicit resolution
31
  * mechanism. This means, for example, that they will not override bespoke `Option` or `Either` instances.
32
  */
33
trait ShapelessInstances {
34
  // - Case classes ----------------------------------------------------------------------------------------------------
35
  // -------------------------------------------------------------------------------------------------------------------
36
  /** Provides an `Encoder` instance for case classes.
37
    *
38
    * Given a case class `D`, this expects an `Encoder` instance for the `HList` type corresponding to `D`. It will
39
    * then simply turn values of type `D` into values of the corresponding `HList`, then let the encoder take it from
40
    * there.
41
    */
42
  implicit def caseClassEncoder[E, D, T, H <: HList](
43
    implicit gen: Generic.Aux[D, H],
44
    er: Lazy[Encoder[E, H, T]]
45
  ): DerivedEncoder[E, D, T] =
46 4
    DerivedEncoder.from(s => er.value.encode(gen.to(s)))
47

48
  /** Similar to [[caseClassEncoder]], but working with `LabelledGeneric` rather than just `Generic`. */
49
  implicit def caseClassEncoderFromLabelled[E, D, T, H <: HList](
50
    implicit generic: LabelledGeneric.Aux[D, H],
51
    hEncoder: Lazy[Encoder[E, H, T]]
52
  ): DerivedEncoder[E, D, T] =
53 0
    DerivedEncoder.from(value => hEncoder.value.encode(generic.to(value)))
54

55
  /** Provides a `Decoder` instance for case classes.
56
    *
57
    * Given a case class `D`, this expects n `Decoder` instance for the `HList` type corresponding to `D`. It will
58
    * then rely on that to turn encoded values into an `HList`, then turn the resulting value into a `D`.
59
    */
60
  implicit def caseClassDecoder[E, D, F, T, H <: HList](
61
    implicit gen: Generic.Aux[D, H],
62
    dr: Lazy[Decoder[E, H, F, T]]
63
  ): DerivedDecoder[E, D, F, T] =
64 4
    DerivedDecoder.from(s => dr.value.decode(s).map(gen.from))
65

66
  /** Similar to [[caseClassDecoder]], but working with `LabelledGeneric` rather than just `Generic`. */
67
  implicit def caseClassDecoderFromLabelled[E, D, F, T, H <: HList](
68
    implicit generic: LabelledGeneric.Aux[D, H],
69
    hDecoder: Lazy[Decoder[E, H, F, T]]
70
  ): DerivedDecoder[E, D, F, T] =
71 0
    DerivedDecoder.from(value => hDecoder.value.decode(value).map(generic.from))
72

73
  // - Sum types -------------------------------------------------------------------------------------------------------
74
  // -------------------------------------------------------------------------------------------------------------------
75
  /** Provides an `Encoder` instance for sum types.
76
    *
77
    * Given a sum type `D`, this expects an `Encoder` instance for the `Coproduct` type corresponding to `D`. It
78
    * will then simply turn values of type `D` into values of the corresponding `Coproduct`, then let the encoder take
79
    * it from there.
80
    */
81
  implicit def sumTypeEncoder[E, D, T, C <: Coproduct](
82
    implicit gen: Generic.Aux[D, C],
83
    er: Lazy[Encoder[E, C, T]]
84
  ): DerivedEncoder[E, D, T] =
85 4
    DerivedEncoder.from(m => er.value.encode(gen.to(m)))
86

87
  /** Provides a `Decoder` instance for sum types.
88
    *
89
    * Given a case class `D`, this expects n `Decoder` instance for the `Coproduct` type corresponding to `D`.
90
    * It will then rely on that to turn encoded values into a `Coproduct`, then turn the resulting value into a `D`.
91
    */
92
  implicit def sumTypeDecoder[E, D, F, T, C <: Coproduct](
93
    implicit gen: Generic.Aux[D, C],
94
    dr: Lazy[Decoder[E, C, F, T]]
95
  ): DerivedDecoder[E, D, F, T] =
96 4
    DerivedDecoder.from(m => dr.value.decode(m).map(gen.from))
97

98
  // - Coproducts ------------------------------------------------------------------------------------------------------
99
  // -------------------------------------------------------------------------------------------------------------------
100
  implicit def cnilDecoder[E, F: IsError, T]: Decoder[E, CNil, F, T] =
101 4
    Decoder.from(_ => Left(IsError[F].fromMessage("Attempting to decode CNil")))
102

103
  implicit def coproductDecoder[E, H, D <: Coproduct, F, T](
104
    implicit dh: Decoder[E, H, F, T],
105
    dt: Decoder[E, D, F, T]
106
  ): Decoder[E, H :+: D, F, T] =
107 4
    Decoder.from(e => dh.decode(e).map(Inl.apply).left.flatMap(_ => dt.decode(e).map(Inr.apply)))
108

109
  @SuppressWarnings(Array("org.wartremover.warts.Throw"))
110
  implicit def cnilEncoder[E, D, T]: Encoder[E, CNil, T] =
111 4
    Encoder.from(_ => throw new IllegalStateException("trying to encode CNil, this should not happen"))
112

113
  implicit def coproductEncoder[E, H, D <: Coproduct, T](
114
    implicit eh: Encoder[E, H, T],
115
    ed: Encoder[E, D, T]
116 4
  ): Encoder[E, H :+: D, T] = Encoder.from {
117 4
    case Inl(h) => eh.encode(h)
118 4
    case Inr(d) => ed.encode(d)
119
  }
120
}

Read our documentation on viewing source code .

Loading