|
1 | 1 | package io.iohk.ethereum.rlp
|
2 | 2 |
|
3 |
| -import shapeless.{HList, HNil, Lazy, ::, LabelledGeneric, <:!<} |
4 |
| -import shapeless.labelled.FieldType |
| 3 | +import shapeless.{HList, HNil, Lazy, ::, LabelledGeneric, <:!<, Witness} |
| 4 | +import shapeless.labelled.{FieldType, field} |
| 5 | +import scala.util.control.NonFatal |
5 | 6 |
|
6 | 7 | /** Automatically derive RLP codecs for case classes. */
|
7 | 8 | object RLPImplicitDerivations {
|
@@ -33,6 +34,23 @@ object RLPImplicitDerivations {
|
33 | 34 | }
|
34 | 35 | }
|
35 | 36 |
|
| 37 | + /** Specialized decoder for case classes that only accepts RLPList for input. */ |
| 38 | + trait RLPListDecoder[T] extends RLPDecoder[T] { |
| 39 | + def decodeList(items: List[RLPEncodeable]): (T, List[FieldInfo]) |
| 40 | + |
| 41 | + override def decode(rlp: RLPEncodeable): T = |
| 42 | + rlp match { |
| 43 | + case list: RLPList => decodeList(list.items.toList)._1 |
| 44 | + case _ => throw new RuntimeException("Expected to decode an RLPList.") |
| 45 | + } |
| 46 | + } |
| 47 | + object RLPListDecoder { |
| 48 | + def apply[T](f: List[RLPEncodeable] => (T, List[FieldInfo])): RLPListDecoder[T] = |
| 49 | + new RLPListDecoder[T] { |
| 50 | + override def decodeList(items: List[RLPEncodeable]) = f(items) |
| 51 | + } |
| 52 | + } |
| 53 | + |
36 | 54 | /** Encoder for the empty list of fields. */
|
37 | 55 | implicit val deriveHNilRLPListEncoder: RLPListEncoder[HNil] =
|
38 | 56 | RLPListEncoder(_ => RLPList() -> Nil)
|
@@ -75,28 +93,122 @@ object RLPImplicitDerivations {
|
75 | 93 | }
|
76 | 94 | }
|
77 | 95 |
|
78 |
| - /** Deriving RLP encoding for a HList of fields where the current field is non-optional. */ |
| 96 | + /** Encoder for a HList of fields where the current field is non-optional. */ |
79 | 97 | implicit def deriveNonOptionHListRLPListEncoder[K, H, T <: HList](implicit
|
80 | 98 | hEncoder: Lazy[RLPEncoder[H]],
|
81 | 99 | tEncoder: Lazy[RLPListEncoder[T]],
|
82 | 100 | ev: H <:!< Option[_]
|
83 | 101 | ): RLPListEncoder[FieldType[K, H] :: T] = {
|
84 | 102 | val hInfo = FieldInfo(isOptional = false)
|
| 103 | + |
85 | 104 | RLPListEncoder { case head :: tail =>
|
86 | 105 | val hRLP = hEncoder.value.encode(head)
|
87 | 106 | val (tRLP, tInfos) = tEncoder.value.encodeList(tail)
|
88 | 107 | (hRLP :: tRLP, hInfo :: tInfos)
|
89 | 108 | }
|
90 | 109 | }
|
91 | 110 |
|
92 |
| - /** Derive an encoder for a case class based on its labelled generic record representation. */ |
| 111 | + /** Encoder for a case class based on its labelled generic record representation. */ |
93 | 112 | implicit def deriveLabelledGenericRLPListEncoder[T, Rec](implicit
|
94 | 113 | // Auto-derived by Shapeless.
|
95 | 114 | generic: LabelledGeneric.Aux[T, Rec],
|
96 | 115 | // Derived by `deriveOptionHListRLPListEncoder` and `deriveNonOptionHListRLPListEncoder`.
|
97 |
| - recEncoder: Lazy[RLPListEncoder[Rec]] |
98 |
| - ): RLPListEncoder[T] = RLPListEncoder { value => |
99 |
| - recEncoder.value.encodeList(generic.to(value)) |
| 116 | + recEncoder: Lazy[RLPEncoder[Rec]] |
| 117 | + ): RLPEncoder[T] = RLPEncoder { value => |
| 118 | + recEncoder.value.encode(generic.to(value)) |
100 | 119 | }
|
101 | 120 |
|
| 121 | + /** Decoder for the empty list of fields. |
| 122 | + * |
| 123 | + * We can ignore extra items in the RLPList as optional fields we don't handle, |
| 124 | + * or extra random data, which we have for example in EIP8 test vectors. |
| 125 | + */ |
| 126 | + implicit val deriveHNilRLPListDecoder: RLPListDecoder[HNil] = |
| 127 | + RLPListDecoder(_ => HNil -> Nil) |
| 128 | + |
| 129 | + /** Decoder for a list of fields in the generic represenation of a case class. |
| 130 | + * |
| 131 | + * This variant deals with trailing optional fields, which may be omitted from |
| 132 | + * the end of RLP lists. |
| 133 | + */ |
| 134 | + implicit def deriveOptionHListRLPListDecoder[K <: Symbol, H, V, T <: HList](implicit |
| 135 | + hDecoder: Lazy[RLPDecoder[H]], |
| 136 | + tDecoder: Lazy[RLPListDecoder[T]], |
| 137 | + // The witness provides access to the Symbols which LabelledGeneric uses |
| 138 | + // to tag the fields with their names, so we can use it to provide better |
| 139 | + // contextual error messages. |
| 140 | + witness: Witness.Aux[K], |
| 141 | + ev: Option[V] =:= H, |
| 142 | + policy: DerivationPolicy |
| 143 | + ): RLPListDecoder[FieldType[K, H] :: T] = { |
| 144 | + val fieldName: String = witness.value.name |
| 145 | + val hInfo = FieldInfo(isOptional = true) |
| 146 | + |
| 147 | + RLPListDecoder { |
| 148 | + case Nil if policy.omitTrailingOptionals => |
| 149 | + val (tail, tInfos) = tDecoder.value.decodeList(Nil) |
| 150 | + val value: H = None |
| 151 | + val head: FieldType[K, H] = field[K](value) |
| 152 | + (head :: tail) -> (hInfo :: tInfos) |
| 153 | + |
| 154 | + case Nil => |
| 155 | + throw new RuntimeException(s"Cannot decode optional '${fieldName}': the RLPList is empty.") |
| 156 | + |
| 157 | + case rlps => |
| 158 | + val (tail, tInfos) = tDecoder.value.decodeList(rlps.tail) |
| 159 | + val value: H = |
| 160 | + try { |
| 161 | + if (policy.omitTrailingOptionals && tInfos.forall(_.isOptional)) { |
| 162 | + // Treat it as a value. We have a decoder for optional fields, so we have to wrap it. |
| 163 | + hDecoder.value.decode(RLPList(rlps.head)) |
| 164 | + } else { |
| 165 | + hDecoder.value.decode(rlps.head) |
| 166 | + } |
| 167 | + } catch { |
| 168 | + case NonFatal(ex) => |
| 169 | + throw new RuntimeException(s"Cannot decode optional '$fieldName' from RLP value: $ex") |
| 170 | + } |
| 171 | + |
| 172 | + val head: FieldType[K, H] = field[K](value) |
| 173 | + (head :: tail) -> (hInfo :: tInfos) |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + /** Decoder for a non-optional field. */ |
| 178 | + implicit def deriveNonOptionHListRLPListDecoder[K <: Symbol, H, T <: HList](implicit |
| 179 | + hDecoder: Lazy[RLPDecoder[H]], |
| 180 | + tDecoder: Lazy[RLPListDecoder[T]], |
| 181 | + witness: Witness.Aux[K], |
| 182 | + ev: H <:!< Option[_] |
| 183 | + ): RLPListDecoder[FieldType[K, H] :: T] = { |
| 184 | + val fieldName: String = witness.value.name |
| 185 | + val hInfo = FieldInfo(isOptional = false) |
| 186 | + |
| 187 | + RLPListDecoder { |
| 188 | + case Nil => |
| 189 | + throw new RuntimeException(s"Cannot decode '${fieldName}': the RLPList is empty.") |
| 190 | + |
| 191 | + case rlps => |
| 192 | + val value: H = |
| 193 | + try { |
| 194 | + hDecoder.value.decode(rlps.head) |
| 195 | + } catch { |
| 196 | + case NonFatal(ex) => |
| 197 | + throw new RuntimeException(s"Cannot decode '$fieldName' from RLP value: $ex") |
| 198 | + } |
| 199 | + val head: FieldType[K, H] = field[K](value) |
| 200 | + val (tail, tInfos) = tDecoder.value.decodeList(rlps.tail) |
| 201 | + (head :: tail) -> (hInfo :: tInfos) |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + /** Decoder for a case class based on its labelled generic record representation. */ |
| 206 | + implicit def deriveLabelledGenericRLPListDecoder[T, Rec](implicit |
| 207 | + // Auto-derived by Shapeless. |
| 208 | + generic: LabelledGeneric.Aux[T, Rec], |
| 209 | + // Derived by `deriveOptionHListRLPListDecoder` and `deriveNonOptionHListRLPListDecoder`. |
| 210 | + recDecoder: Lazy[RLPDecoder[Rec]] |
| 211 | + ): RLPDecoder[T] = RLPDecoder { rlp => |
| 212 | + generic.from(recDecoder.value.decode(rlp)) |
| 213 | + } |
102 | 214 | }
|
0 commit comments