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

dev.tauri.seals.scodec.StreamCodecs.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2016-2020 Daniel Urban and contributors listed in AUTHORS
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dev.tauri.seals
package scodec

import cats.implicits._

import fs2.{ Stream, Pull, Pipe, RaiseThrowable }

import _root_.scodec.{ Err, Attempt, Decoder, DecodeResult }
import _root_.scodec.bits.BitVector
import _root_.scodec.stream.{ StreamEncoder, StreamDecoder }
import _root_.scodec.stream.CodecError

object StreamCodecs extends StreamCodecs

trait StreamCodecs {

  // TODO: implicit?
  def streamEncoderFromReified[A](implicit A: Reified[A]): StreamEncoder[A] = {
    Codecs.encoderFromReified[Model].encode(A.model).fold(
      err => { StreamEncoder.raiseError(err) },
      bv => {
        StreamEncoder.emit[A](bv) ++ StreamEncoder.many[A](Codecs.encoderFromReified(A))
      }
    )
  }

  // TODO: implicit?
  def streamDecoderFromReified[A](implicit A: Reified[A]): StreamDecoder[A] = {
    StreamDecoder.once(Codecs.decoderFromReified[Model]).flatMap { model =>
      if (model compatible A.model) {
        // TODO: manyChunked?
        StreamDecoder.many(Codecs.decoderFromReified(A))
      } else {
        StreamDecoder.raiseError(Err(sh"incompatible models: expected '${A.model}', got '${model}'"))
      }
    }
  }

  def pipe[F[_], A](implicit A: Reified[A], F: RaiseThrowable[F]): Pipe[F, BitVector, A] = {

    def decodeOne[R](s: Stream[F, BitVector], decoder: Decoder[R]): Pull[F, Nothing, Option[(R, Stream[F, BitVector])]] = {
      def go(buff: BitVector, s: Stream[F, BitVector]): Pull[F, Nothing, Option[(R, Stream[F, BitVector])]] = {
        for {
          opt <- s.pull.uncons1
          res <- opt match {
            case Some((bv, rest)) =>
              val data = buff ++ bv
              decoder.decode(data) match {
                case Attempt.Failure(Err.InsufficientBits(_, _, _)) =>
                  go(data, rest)
                case Attempt.Failure(err) =>
                  Pull.raiseError(CodecError(err)) : Pull[F, Nothing, Option[(R, Stream[F, BitVector])]]
                case Attempt.Successful(DecodeResult(a, rem)) =>
                  Pull.pure(Some((a, Stream(rem) ++ rest)))
              }
            case None =>
              Pull.pure(None)
          }
        } yield res
      }

      go(BitVector.empty, s)
    }

    def decodeMany[R](decoder: Decoder[R]): Stream[F, BitVector] => Pull[F, R, Unit] = {
      Pull.loop[F, R, Stream[F, BitVector]] { s =>
        for {
          opt <- decodeOne[R](s, decoder)
          cont <- opt match {
            case Some((r, tail)) =>
              Pull.output1(r) >> Pull.pure(Some(tail))
            case None =>
              Pull.pure(None)
          }
        } yield cont
      }.map(_.void) // Pull.loop always finishes with None, so we can ignore the result
    }

    def decode(s: Stream[F, BitVector]): Pull[F, A, Unit] = {
      decodeOne[Model](s, Codecs.decoderFromReified[Model]).flatMap {
        case Some((model, tail)) =>
          if (model compatible A.model) {
            decodeMany(Codecs.decoderFromReified[A])(tail)
          } else {
            Pull.raiseError(CodecError(Err(sh"incompatible models: expected '${A.model}', got '${model}'")))
          }
        case None =>
          Pull.raiseError(CodecError(Err("invalid stream: no model header found")))
      }
    }

    s => {
      decode(s).stream
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy