All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scodec.package.scala Maven / Gradle / Ivy

import language.higherKinds

import shapeless._
import ops.hlist.{ Prepend, Init, Last, Length, Split, Mapper }
import poly._

/**
 * Combinator library for working with binary data.
 *
 * The primary abstraction of this library is [[Codec]], which provides the ability to encode/decode values to/from binary.
 *
 * There are more general abstractions though, such as [[Encoder]] and [[Decoder]]. There's also [[GenCodec]] which extends
 * both `Encoder` and `Decoder` but allows the types to vary. Given these more general abstractions, a `Codec[A]` can be
 * represented as a `GenCodec[A, A]`.
 *
 * The more general abstractions are important because they allow operations on codecs that would not otherwise be possible.
 * For example, given a `Codec[A]`, mapping a function `A => B` over the codec yields a `GenCodec[A, B]`. Without the
 * more general abstractions, `map` is impossible to define (e.g., how would `codec.map(f).encode(b)` be implemented?).
 * Given a `GenCodec[A, B]`, the encoding functionality can be ignored by treating it as a `Decoder[B]`, or the encoding
 * type can be changed via `contramap`. If after further transformations, the two types to `GenCodec` are equal, we can
 * reconstitute a `Codec` from the `GenCodec` by calling `fuse`.
 *
 * See the [[codecs]] package object for pre-defined codecs for many common data types and combinators for building larger
 * codecs out of smaller ones.
 *
 * For the categorically minded, note the following:
 *  - `Decoder` is a monad
 *  - `Encoder` is a contravariant functor
 *  - `GenCodec` is a profunctor
 *  - `Codec` is an invariant functor
 */
package object scodec {

  /**
   * Provides method syntax for working with a type constructor that has a [[Transform]] typeclass instance.
   *
   * @param self Supports [[TransformSyntax]].
   */
  implicit class TransformSyntax[F[_], A](val self: F[A])(implicit t: Transform[F]) {

    /**
     * Transforms using two functions, `A => Attempt[B]` and `B => Attempt[A]`.
     * @group combinators
     */
    def exmap[B](f: A => Attempt[B], g: B => Attempt[A]): F[B] = t.exmap(self, f, g)

    /**
     * Curried version of `exmap`.
     * @group combinators
     */
    def exmapc[B](f: A => Attempt[B])(g: B => Attempt[A]): F[B] = t.exmap(self, f, g)

    /**
     * Transforms using the isomorphism described by two functions, `A => B` and `B => A`.
     * @group combinators
     */
    def xmap[B](f: A => B, g: B => A): F[B] = t.xmap(self, f, g)

    /**
     * Curried version of `xmap`.
     * @group combinators
     */
    def xmapc[B](f: A => B)(g: B => A): F[B] = t.xmap(self, f, g)

    /**
     * Transforms using two functions, `A => Attempt[B]` and `B => A`.
     *
     * The supplied functions form an injection from `B` to `A`. Hence, this method converts from
     * a larger to a smaller type. Hence, the name `narrow`.
     * @group combinators
     */
    def narrow[B](f: A => Attempt[B], g: B => A): F[B] = t.narrow(self, f, g)

    /**
     * Curried version of `narrow`.
     * @group combinators
     */
    def narrowc[B](f: A => Attempt[B])(g: B => A): F[B] = t.narrow(self, f, g)

    /**
     * Transforms using two functions, `A => B` and `B => Attempt[A]`.
     *
     * The supplied functions form an injection from `A` to `B`. Hence, this method converts from
     * a smaller to a larger type. Hence, the name `widen`.
     * @group combinators
     */
    def widen[B](f: A => B, g: B => Attempt[A]): F[B] = t.widen(self, f, g)

    /**
     * Curried version of `widen`.
     * @group combinators
     */
    def widenc[B](f: A => B)(g: B => Attempt[A]): F[B] = t.widen(self, f, g)

    /**
     * Transforms using two functions, `A => B` and `B => Option[A]`.
     *
     * Particularly useful when combined with case class apply/unapply. E.g., `widenOpt(fa, Foo.apply, Foo.unapply)`.
     *
     * @group combinators
     */
    def widenOpt[B](f: A => B, g: B => Option[A]): F[B] = t.widenOpt(self, f, g)

    /**
     * Curried version of `widenOpt`.
     * @group combinators
     */
    def widenOptc[B](f: A => B)(g: B => Option[A]): F[B] = t.widenOpt(self, f, g)

    /**
     * Transforms using two functions, `A => B` and `B => Option[A]`.
     *
     * Particularly useful when combined with case class apply/unapply. E.g., `pxmap(fa, Foo.apply, Foo.unapply)`.
     *
     * @group combinators
     */
    @deprecated("Use widenOpt instead", "1.7.0")
    def pxmap[B](f: A => B, g: B => Option[A]): F[B] = widenOpt(f, g)

    /**
     * Transforms using implicitly available evidence that such a transformation is possible.
     *
     * Typical transformations include converting:
     *  - an `F[L]` for some `L <: HList` to/from an `F[CC]` for some case class `CC`, where the types in the case class are
     *    aligned with the types in `L`
     *  - an `F[C]` for some `C <: Coproduct` to/from an `F[SC]` for some sealed class `SC`, where the component types in
     *    the coproduct are the leaf subtypes of the sealed class.
     * @group combinators
     */
    def as[B](implicit as: Transformer[A, B]): F[B] = as(self)
  }

  /** Provides common operations on a `Codec[HList]`. */
  final implicit class HListCodecEnrichedWithHListSupport[L <: HList](val self: Codec[L]) extends AnyVal {
    import codecs.HListCodec

    /**
     * When called on a `Codec[L]` for some `L <: HList`, returns a new codec representing `Codec[B :: L]`.
     * That is, this operator is a codec-level `HList` prepend operation.
     * @param codec codec to prepend
     * @group hlist
     */
    def ::[B](codec: Codec[B]): Codec[B :: L] = HListCodec.prepend(codec, self)

    /**
     * When called on a `Codec[L]` for some `L <: HList`, returns a new codec that encodes/decodes
     * `B :: L` but only returns `L`.  HList equivalent of `~>`.
     * @group hlist
     */
    def :~>:[B](codec: Codec[B])(implicit ev: Unit =:= B): Codec[L] = codec.dropLeft(self)

    /**
     * Creates a new codec with all unit values filtered out.
     * @group hlist
     */
    def dropUnits[M <: HList](implicit du: codecs.DropUnits.Aux[L, M]): Codec[M] =
      HListCodec.dropUnits[L, M](self)

    /**
     * When called on a `Codec[L]` for some `L <: HList`, returns a new codec that encodes/decodes
     * the `HList L` followed by a `B`.
     * That is, this operator is a codec-level `HList` append operation.
     * @group hlist
     */
    def :+[B, LB <: HList](codec: Codec[B])(implicit
      prepend: Prepend.Aux[L, B :: HNil, LB],
      init: Init.Aux[LB, L],
      last: Last.Aux[LB, B]
    ): Codec[LB] = HListCodec.append(self, codec)

    /**
     * When called on a `Codec[L]` for some `L <: HList`, returns a new codec that encodes/decodes
     * the `HList K` followed by the `HList L`.
     * @group hlist
     */
    def :::[K <: HList, KL <: HList, KLen <: Nat](k: Codec[K])(implicit
      prepend: Prepend.Aux[K, L, KL],
      lengthK: Length.Aux[K, KLen],
      split: Split.Aux[KL, KLen, K, L]
    ): Codec[KL] = HListCodec.concat(k, self)

    /**
     * When called on a `Codec[L]` for some `L <: HList`, returns a new codec that encodes/decodes
     * the `HList L` followed by the `HList M`, where the latter is encoded/decoded with the codec
     * returned from applying `L` to `f`.
     * @group hlist
     */
    def flatConcat[M <: HList, LM <: HList, LLen <: Nat](f: L => Codec[M])(implicit
      prepend: Prepend.Aux[L, M, LM],
      lengthK: Length.Aux[L, LLen],
      split: Split.Aux[LM, LLen, L, M]
    ): Codec[LM] = HListCodec.flatConcat(self, f)

    /**
     * When called on a `Codec[L]` for some `L <: HList`, returns a new codec that encodes/decodes
     * the `HList L` followed by the value `A`, where the latter is encoded/decoded with the codec
     * returned from applying `L` to `f`.
     * @group hlist
     */
    def flatAppend[A, LA <: HList, Len <: Nat](f: L => Codec[A])(implicit
      prepend: Prepend.Aux[L, A :: HNil, LA],
      length: Length.Aux[L, Len],
      split: Split.Aux[LA, Len, L, A :: HNil]
    ): Codec[LA] = HListCodec.flatAppend(self, f)

    /**
     * Polymorphic function version of `xmap`.
     *
     * When called on a `Codec[L]` for some `L <: HList`, returns a new codec that's the result of
     * xmapping with `p` and `q`, using `p` to convert from `L` to `M` and using `q` to convert from
     * `M` to `L`.
     *
     * @param p polymorphic function that converts from `L` to `M`
     * @param q polymorphic function that converts from `M` to `L`
     * @group generic
     */
    def polyxmap[M <: HList](p: Poly, q: Poly)(implicit lToM: Mapper.Aux[p.type, L, M], mToL: Mapper.Aux[q.type, M, L]): Codec[M] =
      self.xmap(_ map p, _ map q)

    /**
     * Polymorphic function version of `xmap` that uses a single polymorphic function in both directions.
     *
     * When called on a `Codec[L]` for some `L <: HList`, returns a new codec that's the result of
     * xmapping with `p` for both forward and reverse directions.
     *
     * @param p polymorphic function that converts from `L` to `M` and from `M` to `L`
     * @group generic
     */
    def polyxmap1[M <: HList](p: Poly)(implicit m: Mapper.Aux[p.type, L, M], m2: Mapper.Aux[p.type, M, L]): Codec[M] =
      polyxmap(p, p)

    /**
     * Supports building a `Codec[M]` for some `HList M` where `M` is the `HList` that results in removing
     * the first `A` from `L`.
     *
     * Example usage: {{{
       case class Flags(x: Boolean, y: Boolean, z: Boolean)
       val c = (bool :: bool :: bool :: ignore(5)).flatPrepend { flgs =>
         conditional(flgs.x, uint8) :: conditional(flgs.y, uint8) :: conditional(flgs.z, uint8)
       }
       c.derive[Flags].from { case x :: y :: z :: HNil => Flags(x.isDefined, y.isDefined, z.isDefined) }
     }}}
     *
     * This codec, the `Codec[L]`, is used for encoding/decoding. When decoding, the first value of type
     * `A` is removed from the `HList`.
     *
     * When encoding, the returned codec computes an `A` value using the supplied
     * function and inserts the computed `A` in to the `HList M`, yielding an `HList L`. That `HList L`
     * is then encoded using the original codec.
     *
     * This method is called `derive` because the value of type `A` is derived from the other fields
     * in the `HList L`.
     *
     * @tparam A type to remove from `L` and derive from the resulting list
     * @group hlist
     */
    def derive[A]: codecs.DeriveHListElementAux[L, A] = new codecs.DeriveHListElementAux[L, A](self)
  }

  /** Provides `HList` related syntax for codecs of any type. */
  final implicit class ValueCodecEnrichedWithHListSupport[A](val self: Codec[A]) extends AnyVal {
    import codecs.HListCodec

    /**
     * When called on a `Codec[A]` where `A` is not a subytpe of `HList`, creates a new codec that encodes/decodes an `HList` of `B :: A :: HNil`.
     * For example, {{{uint8 :: utf8}}} has type `Codec[Int :: String :: HNil]`.
     * @group hlist
     */
    def ::[B](codecB: Codec[B]): Codec[B :: A :: HNil] =
      codecB :: self :: HListCodec.hnilCodec

    /**
     * When called on a `Codec[A]`, returns a new codec that encodes/decodes `B :: A :: HNil`.
     * HList equivalent of `~>`.
     * @group hlist
     */
    def :~>:[B](codecB: Codec[B])(implicit ev: Unit =:= B): Codec[A :: HNil] =
      codecB :~>: self.hlist

    /**
     * Creates a new codec that encodes/decodes an `HList` type of `A :: L` given a function `A => Codec[L]`.
     * This allows later parts of an `HList` codec to be dependent on earlier values.
     * @group hlist
     */
    def flatPrepend[L <: HList](f: A => Codec[L]): Codec[A :: L] = HListCodec.flatPrepend(self, f)

    /**
     * Creates a new codec that encodes/decodes an `HList` type of `A :: L` given a function `A => Codec[L]`.
     * This allows later parts of an `HList` codec to be dependent on earlier values.
     * Operator alias for `flatPrepend`.
     * @group hlist
     */
    def >>:~[L <: HList](f: A => Codec[L]): Codec[A :: L] = flatPrepend(f)

    /**
     * Creates a new codec that encodes/decodes an `HList` type of `A :: B :: HNil` given a function `A => Codec[B]`.
     * If `B` is an `HList` type, consider using `flatPrepend` instead, which avoids nested `HLists`.
     * This is the direct `HList` equivalent of `flatZip`.
     * @group hlist
     */
    def flatZipHList[B](f: A => Codec[B]): Codec[A :: B :: HNil] = flatPrepend(f andThen (_.hlist))
  }

  /** Provides syntax related to generic programming for codecs of any type. */
  final implicit class ValueCodecEnrichedWithGenericSupport[A](val self: Codec[A]) extends AnyVal {

    /**
     * Polymorphic function version of `xmap`.
     *
     * When called on a `Codec[A]` where `A` is not a subytpe of `HList`, returns a new codec that's the result of
     * xmapping with `p` and `q`, using `p` to convert from `A` to `B` and using `q` to convert from
     * `B` to `A`.
     *
     * @param p polymorphic function that converts from `A` to `B`
     * @param q polymorphic function that converts from `B` to `A`
     * @group generic
     */
    def polyxmap[B](p: Poly, q: Poly)(implicit aToB: Case.Aux[p.type, A :: HNil, B], bToA: Case.Aux[q.type, B :: HNil, A]): Codec[B] =
      self.xmap(aToB, bToA)

    /**
     * Polymorphic function version of `xmap` that uses a single polymorphic function in both directions.
     *
     * When called on a `Codec[A]` where `A` is not a subytpe of `HList`, returns a new codec that's the result of
     * xmapping with `p` for both forward and reverse directions.
     *
     * @param p polymorphic function that converts from `A` to `B` and from `B` to `A`
     * @group generic
     */
    def polyxmap1[B](p: Poly)(implicit aToB: Case.Aux[p.type, A :: HNil, B], bToA: Case.Aux[p.type, B :: HNil, A]): Codec[B] =
      polyxmap(p, p)
  }

  /** Provides additional methods on `HList`s. */
  final implicit class EnrichedHList[L <: HList](val self: L) extends AnyVal {
    /**
     * Converts an `HList` of codecs in to a single codec.
     * That is, converts `Codec[X0] :: Codec[X1] :: ... :: Codec[Xn] :: HNil` in to a `Codec[X0 :: X1 :: ... :: Xn :: HNil].
     */
    def toCodec(implicit to: codecs.ToHListCodec[L]): to.Out = to(self)
  }

  /** Provides methods specific to encoders of Shapeless coproducts. */
  final implicit class EnrichedCoproductEncoder[C <: Coproduct](val self: Encoder[C]) extends AnyVal {
    /**
     * When called on a `Encoder[C]` where `C` is a coproduct containing type `A`, converts to an `Encoder[A]`.
     * @group coproduct
     */
    def selectEncoder[A](implicit inj: ops.coproduct.Inject[C, A]): Encoder[A] = self.contramap { a => Coproduct[C](a) }
  }

  /** Provides methods specific to decoders of Shapeless coproducts. */
  final implicit class EnrichedCoproductDecoder[C <: Coproduct](val self: Decoder[C]) extends AnyVal {
    /**
     * When called on a `Decoder[C]` where `C` is a coproduct containing type `A`, converts to a `Decoder[Option[A]]`.
     * @group coproduct
     */
    def selectDecoder[A](implicit sel: ops.coproduct.Selector[C, A]): Decoder[Option[A]] = self.map { c => c.select[A] }
  }

  final implicit class Tuple2CodecSupport[A](val self: Codec[A]) extends AnyVal {
    def ~~[B](B: Codec[B]): codecs.TupleCodec[A,B] = new codecs.TupleCodec[A,B](self, B)
  }

  /** Universally quantified transformation of a `Codec` to a `Codec`. */
  type CodecTransformation = Codec ~> Codec

  /** Companion for [[CodecTransformation]]. */
  object CodecTransformation {
    object Id extends CodecTransformation {
      def apply[X](c: Codec[X]): Codec[X] = c
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy