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

mongo4cats.circe.MongoJsonCodecs.scala Maven / Gradle / Ivy

/*
 * Copyright 2020 Kirill5k
 *
 * 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 mongo4cats.circe

import io.circe.{Decoder, Encoder, Json, JsonObject}
import mongo4cats.bson._
import mongo4cats.bson.json._
import mongo4cats.bson.syntax._
import mongo4cats.codecs.MongoCodecProvider
import mongo4cats.errors.MongoJsonParsingException
import org.bson.codecs.configuration.CodecProvider

import java.time.{Instant, LocalDate}
import java.util.{Base64, UUID}
import scala.reflect.ClassTag
import scala.util.Try

trait MongoJsonCodecs {
  private val emptyJsonObject = Json.fromJsonObject(JsonObject.empty)

  implicit def deriveJsonBsonValueDecoder[A](implicit d: Decoder[A]): BsonValueDecoder[A] =
    bson => CirceJsonMapper.fromBson(bson).flatMap(d.decodeJson).toOption

  implicit def deriveJsonBsonValueEncoder[A](implicit e: Encoder[A]): BsonValueEncoder[A] =
    value => CirceJsonMapper.toBson(e(value))

  implicit val documentEncoder: Encoder[Document] =
    Encoder.encodeJson.contramap[Document](d => CirceJsonMapper.fromBson(BsonValue.document(d)).getOrElse(emptyJsonObject))

  implicit val documentDecoder: Decoder[Document] =
    Decoder.decodeJson.emap(j => CirceJsonMapper.toBson(j).asDocument.toRight(s"$j is not a valid document"))

  implicit val objectIdEncoder: Encoder[ObjectId] =
    Encoder.encodeJson.contramap[ObjectId](CirceJsonMapper.objectIdToJson)

  implicit val objectIdDecoder: Decoder[ObjectId] =
    Decoder.decodeJson.emap(id => CirceJsonMapper.jsonToObjectIdString(id).toRight(s"$id is not a valid id").flatMap(ObjectId.from))

  implicit val instantEncoder: Encoder[Instant] =
    Encoder.encodeJson.contramap[Instant](CirceJsonMapper.instantToJson)

  implicit val instantDecoder: Decoder[Instant] =
    Decoder.decodeJson.emap { instantObj =>
      CirceJsonMapper
        .jsonToDateString(instantObj)
        .flatMap(s => Try(Instant.parse(s)).toOption)
        .toRight(s"$instantObj is not a valid instant object")
    }

  implicit val localDateEncoder: Encoder[LocalDate] =
    Encoder.encodeJson.contramap[LocalDate](CirceJsonMapper.localDateToJson)

  implicit val localDateDecoder: Decoder[LocalDate] =
    Decoder.decodeJson.emap { dateObj =>
      CirceJsonMapper
        .jsonToDateString(dateObj)
        .map(_.slice(0, 10))
        .flatMap(s => Try(LocalDate.parse(s)).toOption)
        .toRight(s"$dateObj is not a valid date object")
    }

  implicit val uuidEncoder: Encoder[UUID] =
    Encoder.encodeJson.contramap[UUID](CirceJsonMapper.uuidToJson)

  implicit val uuidDecoder: Decoder[UUID] =
    Decoder.decodeJson.emapTry(uuidObj => Try(CirceJsonMapper.jsonToUuid(uuidObj)))

  implicit val binaryEncoder: Encoder[Array[Byte]] =
    Encoder.encodeJson.contramap[Array[Byte]](CirceJsonMapper.binaryArrayToJson)

  implicit val binaryDecoder: Decoder[Array[Byte]] =
    Decoder.decodeJson.emap[Array[Byte]] { json =>
      CirceJsonMapper
        .jsonToBinaryBase64(json)
        .toRight(s"$json is not a valid binary object")
        .flatMap(base64 => Try(Base64.getDecoder.decode(base64)).toEither.left.map(_.getMessage))
    }

  implicit def deriveCirceCodecProvider[T: ClassTag](implicit enc: Encoder[T], dec: Decoder[T]): MongoCodecProvider[T] =
    new MongoCodecProvider[T] {
      override def get: CodecProvider = codecProvider[T](
        _.toBson,
        CirceJsonMapper
          .fromBson(_)
          .flatMap(j => dec.decodeJson(j).left.map(e => MongoJsonParsingException(e.getMessage, Some(j.noSpaces))))
      )
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy