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
|
|
}
|