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

scodec.Encoder.scala Maven / Gradle / Ivy

package scodec

import scala.annotation.unchecked.uncheckedVariance

import scalaz.{ \/, \/-, -\/, Contravariant, Corepresentable }
import \/.left

import scodec.bits.BitVector

/**
 * Supports encoding a value of type `A` to a `BitVector`.
 *
 * @groupname primary Primary Members
 * @groupprio primary 0
 *
 * @groupname combinators Basic Combinators
 * @groupprio combinators 10
 *
 * @groupname coproduct Coproduct Support
 * @groupprio coproduct 13
 */
trait Encoder[-A] { self =>

  /**
   * Attempts to encode the specified value in to a bit vector.
   *
   * @param value value to encode
   * @param return error or binary encoding of the value
   * @group primary
   */
  def encode(value: A): String \/ BitVector

  /**
   * Encodes the specified value in to a bit vector, throwing an
   * `IllegalArgumentException` if encoding fails.
   *
   * @param value value to encode
   * @param return error or binary encoding of the value
   * @throws IllegalArgumentException upon encoding failure
   * @group primary
   */
  final def encodeValid(value: A): BitVector =
    encode(value) valueOr { err => throw new IllegalArgumentException(err) }

  /**
   * Converts this encoder to an `Encoder[B]` using the supplied `B => A`.
   * @group combinators
   */
  def contramap[B](f: B => A): Encoder[B] = new Encoder[B] {
    def encode(b: B) = self.encode(f(b))
  }

  /**
   * Converts this encoder to an `Encoder[B]` using the supplied partial
   * function from `B` to `A`. The encoding will fail for any `B` that
   * `f` maps to `None`.
   * @group combinators
   */
  def pcontramap[B](f: B => Option[A]): Encoder[B] = new Encoder[B] {
    def encode(b: B): String \/ BitVector =
      f(b).map(self.encode).getOrElse(left(s"extraction failure: $b"))
  }

  /**
   * Converts this encoder to an `Encoder[B]` using the supplied `B => String \/ A`.
   * @group combinators
   */
  def econtramap[B](f: B => String \/ A): Encoder[B] = new Encoder[B] {
    def encode(b: B) = f(b) flatMap self.encode
  }

  /**
   * Converts this encoder to a new encoder that compacts the generated bit vector before returning it
   * @group combinators
   */
  def compact: Encoder[A] = new Encoder[A] {
    def encode(a: A) = self.encode(a).map { _.compact }
  }

  /**
   * Gets this as an `Encoder`.
   * @group combinators
   */
  def asEncoder: Encoder[A] = this

  /**
   * Converts this to a codec that fails decoding with an error.
   * @group combinators
   */
  def encodeOnly: Codec[A @uncheckedVariance] = new Codec[A] {
    def encode(a: A) = self.encode(a)
    def decode(bits: BitVector) = \/.left("decoding not supported")
  }
}

/** Provides functions for working with encoders. */
trait EncoderFunctions {

  /** Encodes the specified value to a bit vector using an implicitly available encoder. */
  final def encode[A: Encoder](a: A): String \/ BitVector = Encoder[A].encode(a)

  /** Encodes the specified value to a bit vector using an implicitly available encoder or throws an `IllegalArgumentException` if encoding fails. */
  final def encodeValid[A: Encoder](a: A): BitVector = Encoder[A].encodeValid(a)

  /** Encodes the specified values, one after the other, to a bit vector using the specified encoders. */
  final def encodeBoth[A, B](encA: Encoder[A], encB: Encoder[B])(a: A, b: B): String \/ BitVector = for {
    encodedA <- encA.encode(a)
    encodedB <- encB.encode(b)
  } yield encodedA ++ encodedB

  /** Encodes all elements of the specified sequence and concatenates the results, or returns the first encountered error. */
  final def encodeSeq[A](enc: Encoder[A])(seq: collection.immutable.Seq[A]): String \/ BitVector = {
    val buf = new collection.mutable.ArrayBuffer[BitVector](seq.size)
    seq foreach { a =>
      enc.encode(a) match {
        case \/-(aa) => buf += aa
        case e @ -\/(_) => return e
      }
    }
    def merge(offset: Int, size: Int): BitVector = size match {
      case 0 => BitVector.empty
      case 1 => buf(offset)
      case n =>
        val half = size / 2
        merge(offset, half) ++ merge(offset + half, half + (if (size % 2 == 0) 0 else 1))
    }
    \/.right(merge(0, buf.size))
  }

  /**
   * Creates an encoder that encodes with each of the specified encoders, returning
   * the first successful result.
   */
  final def choiceEncoder[A](encoders: Encoder[A]*): Encoder[A] = new Encoder[A] {
    def encode(a: A) = {
      @annotation.tailrec def go(rem: List[Encoder[A]], lastErr: String): String \/ BitVector = rem match {
        case Nil => \/.left(lastErr)
        case hd :: tl =>
          hd.encode(a) match {
            case res @ \/-(_) => res
            case -\/(err) => go(tl, err)
          }
      }
      go(encoders.toList, "no encoders provided")
    }
  }
}

/** Companion for [[Encoder]]. */
object Encoder extends EncoderFunctions {

  /** Provides syntaax for summoning an `Encoder[A]` from implicit scope. */
  def apply[A](implicit enc: Encoder[A]): Encoder[A] = enc

  def instance[A](f: A => String \/ BitVector): Encoder[A] = new Encoder[A] {
    def encode(value: A) = f(value)
  }

  implicit val contravariantInstance: Contravariant[Encoder] = new Contravariant[Encoder] {
    def contramap[A, B](e: Encoder[A])(f: B => A) = e contramap f
  }

  implicit val corepresentableInstance: Corepresentable[Encoder, String \/ BitVector] = new Corepresentable[Encoder, String \/ BitVector] {
    def corep[A](f: A => String \/ BitVector): Encoder[A] = Encoder.instance(f)
    def uncorep[A](f: Encoder[A]): A => String \/ BitVector = f.encode
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy