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

com.wavesplatform.api.http.ApiMarshallers.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.api.http

import akka.NotUsed
import akka.http.scaladsl.marshalling.*
import akka.http.scaladsl.model.MediaTypes.{`application/json`, `text/plain`}
import akka.http.scaladsl.model.*
import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, PredefinedFromEntityUnmarshallers, Unmarshaller}
import akka.http.scaladsl.util.FastFuture
import akka.stream.scaladsl.{Flow, Source}
import akka.util.ByteString
import com.fasterxml.jackson.core.util.ByteArrayBuilder
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider
import com.wavesplatform.api.http.ApiMarshallers.writeToBytes
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.transaction.smart.script.trace.TracedResult
import play.api.libs.json.*

import scala.util.Using
import scala.util.control.Exception.nonFatalCatch
import scala.util.control.NoStackTrace

case class PlayJsonException(
    cause: Option[Throwable] = None,
    errors: scala.collection.Seq[(JsPath, scala.collection.Seq[JsonValidationError])] = Seq.empty
) extends IllegalArgumentException
    with NoStackTrace

trait ApiMarshallers extends JsonFormats {
  import akka.http.scaladsl.marshalling.PredefinedToResponseMarshallers.*

  implicit lazy val ApiErrorMarshaller: ToResponseMarshaller[ApiError] =
    fromStatusCodeAndValue[StatusCode, JsValue].compose(ae => (ae.code, ae.json))

  implicit lazy val ValidationErrorMarshaller: ToResponseMarshaller[ValidationError] =
    ApiErrorMarshaller.compose(ve => ApiError.fromValidationError(ve))

  def tracedResultMarshaller[A](includeTrace: Boolean)(implicit writes: OWrites[A]): ToResponseMarshaller[TracedResult[ApiError, A]] =
    fromStatusCodeAndValue[StatusCode, JsValue]
      .compose(ae =>
        (
          ae.resultE.fold(_.code, _ => StatusCodes.OK),
          ae.resultE.fold(_.json, writes.writes) ++ (if (includeTrace) Json.obj("trace" -> ae.trace.map(_.loggedJson)) else Json.obj())
        )
      )

  private[this] lazy val jsonStringUnmarshaller =
    Unmarshaller.byteStringUnmarshaller
      .forContentTypes(`application/json`)
      .mapWithCharset {
        case (ByteString.empty, _) => throw Unmarshaller.NoContentException
        case (data, charset)       => data.decodeString(charset.nioCharset.name)
      }

  private[this] lazy val jsonByteStringMarshaller =
    Marshaller.byteStringMarshaller(`application/json`)

  private[this] lazy val customJsonByteStringMarshaller =
    Marshaller.byteStringMarshaller(CustomJson.jsonWithNumbersAsStrings)

  private[this] lazy val jsonStringMarshaller =
    Marshaller.stringMarshaller(`application/json`)

  private[this] lazy val customJsonStringMarshaller =
    Marshaller.stringMarshaller(CustomJson.jsonWithNumbersAsStrings)

  implicit def playJsonUnmarshaller[A](implicit reads: Reads[A]): FromEntityUnmarshaller[A] =
    jsonStringUnmarshaller.map { data =>
      val json = nonFatalCatch.withApply(t => throw PlayJsonException(cause = Some(t)))(Json.parse(data))

      json.validate[A] match {
        case JsSuccess(value, _) => value
        case JsError(errors)     => throw PlayJsonException(errors = errors)
      }
    }

  implicit val byteStrUnmarshaller: Unmarshaller[String, ByteStr] = Unmarshaller.strict[String, ByteStr] { s =>
    ByteStr.decodeBase58(s).get
  }

  // preserve support for extracting plain strings from requests
  implicit val stringUnmarshaller: FromEntityUnmarshaller[String] = PredefinedFromEntityUnmarshallers.stringUnmarshaller
  implicit val intUnmarshaller: FromEntityUnmarshaller[Int]       = stringUnmarshaller.map(_.toInt)

  implicit def playJsonMarshaller[A](implicit writes: Writes[A], jsValueToString: JsValue => String = Json.stringify): ToEntityMarshaller[A] =
    Marshaller.oneOf(
      jsonStringMarshaller
        .compose(jsValueToString)
        .compose(writes.writes),
      customJsonStringMarshaller
        .compose(CustomJson.writeValueAsString)
        .compose(writes.writes)
    )

  implicit def jacksonMarshaller[A](implicit ser: Boolean => JsonSerializer[A]): ToEntityMarshaller[A] =
    Marshaller.oneOf(
      jsonByteStringMarshaller
        .compose(v => ByteString.fromArrayUnsafe(writeToBytes[A](v)(ser(false)))),
      customJsonByteStringMarshaller
        .compose(v => ByteString.fromArrayUnsafe(writeToBytes[A](v)(ser(true))))
    )

  // preserve support for using plain strings as request entities
  implicit val stringMarshaller: ToEntityMarshaller[String] = PredefinedToEntityMarshallers.stringMarshaller(`text/plain`)

  private def selectMarshallingForContentType[T](marshallings: Seq[Marshalling[T]], contentType: ContentType): Option[() => T] = {
    contentType match {
      case _: ContentType.Binary | _: ContentType.WithFixedCharset | _: ContentType.WithMissingCharset =>
        marshallings collectFirst { case Marshalling.WithFixedContentType(`contentType`, marshal) => marshal }
      case ContentType.WithCharset(mediaType, charset) =>
        marshallings collectFirst {
          case Marshalling.WithFixedContentType(`contentType`, marshal) => marshal
          case Marshalling.WithOpenCharset(`mediaType`, marshal)        => () => marshal(charset)
        }
    }
  }

  def playJsonStreamMarshaller(
      prefix: String = "[",
      delimiter: String = ",",
      suffix: String = "]"
  ): ToResponseMarshaller[Source[JsValue, NotUsed]] =
    jsonStreamMarshaller(playJsonMarshaller[JsValue])(prefix, delimiter, suffix)

  def jacksonStreamMarshaller[A](
      prefix: String = "[",
      delimiter: String = ",",
      suffix: String = "]"
  )(implicit ser: Boolean => JsonSerializer[A]): ToResponseMarshaller[Source[A, NotUsed]] =
    jsonStreamMarshaller(jacksonMarshaller[A])(prefix, delimiter, suffix)

  private def jsonStreamMarshaller[A](
      marshaller: ToEntityMarshaller[A]
  )(prefix: String, delimiter: String, suffix: String): Marshaller[Source[A, NotUsed], HttpResponse] = {
    val bsm             = marshaller.map(_.dataBytes)
    val framingRenderer = Flow[ByteString].intersperse(ByteString(prefix), ByteString(delimiter), ByteString(suffix))
    Marshaller[Source[A, NotUsed], HttpResponse] { implicit ec => source =>
      val availableMarshallingsPerElement = source.mapAsync(1) { t =>
        bsm(t)(ec)
      }
      FastFuture.successful(List(`application/json`, CustomJson.jsonWithNumbersAsStrings).map { contentType =>
        Marshalling.WithFixedContentType(
          contentType,
          () => {
            val bestMarshallingPerElement = availableMarshallingsPerElement map { marshallings =>
              selectMarshallingForContentType(marshallings, contentType)
                .orElse {
                  marshallings collectFirst { case Marshalling.Opaque(marshal) => marshal }
                }
                .getOrElse(throw new NoStrictlyCompatibleElementMarshallingAvailableException[JsValue](contentType, marshallings))
            }

            val marshalledElements: Source[ByteString, NotUsed] =
              bestMarshallingPerElement
                .flatMapConcat(_.apply()) // marshal!
                .via(framingRenderer)

            HttpResponse(entity = HttpEntity(contentType, marshalledElements))
          }
        )
      })
    }
  }
}

object ApiMarshallers extends ApiMarshallers {
  private lazy val jsonFactory = new JsonFactory()

  def writeToBytes[A](value: A)(implicit ser: JsonSerializer[A]): Array[Byte] = {
    Using.resource(new ByteArrayBuilder(jsonFactory._getBufferRecycler())) { bb =>
      Using.resource(jsonFactory.createGenerator(bb)) { gen =>
        ser.serialize(value, gen, new DefaultSerializerProvider.Impl)
        gen.flush()
        bb.toByteArray
      }
    }((bb: ByteArrayBuilder) => bb.release())
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy