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

.grackle-circe_2.13.0.23.0.source-code.circemapping.scala Maven / Gradle / Ivy

The newest version!
// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
// Copyright (c) 2016-2023 Grackle Contributors
//
// 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 grackle
package circe

import scala.collection.Factory

import cats.MonadThrow
import cats.implicits._
import fs2.Stream
import io.circe.Json
import io.circe.Encoder
import org.tpolecat.sourcepos.SourcePos

import syntax._
import Cursor.DeferredCursor
import ScalarType._

abstract class CirceMapping[F[_]](implicit val M: MonadThrow[F]) extends Mapping[F] with CirceMappingLike[F]

trait CirceMappingLike[F[_]] extends Mapping[F] {

  // Syntax to allow Circe-specific root effects
  implicit class CirceMappingRootEffectSyntax(self: RootEffect.type) {
    def computeJson(fieldName: String)(effect: (Path, Env) => F[Result[Json]])(implicit pos: SourcePos): RootEffect =
      self.computeCursor(fieldName)((p, e) => effect(p, e).map(_.map(circeCursor(p, e, _))))

    def computeEncodable[A](fieldName: String)(effect: (Path, Env) => F[Result[A]])(implicit pos: SourcePos, enc: Encoder[A]): RootEffect =
      computeJson(fieldName)((p, e) => effect(p, e).map(_.map(enc(_))))
  }

  implicit class CirceMappingRootStreamSyntax(self: RootStream.type) {
    def computeJson(fieldName: String)(effect: (Path, Env) => Stream[F, Result[Json]])(implicit pos: SourcePos): RootStream =
      self.computeCursor(fieldName)((p, e) => effect(p, e).map(_.map(circeCursor(p, e, _))))

    def computeEncodable[A](fieldName: String)(effect: (Path, Env) => Stream[F, Result[A]])(implicit pos: SourcePos, enc: Encoder[A]): RootStream =
      computeJson(fieldName)((p, e) => effect(p, e).map(_.map(enc(_))))
  }

  def circeCursor(path: Path, env: Env, value: Json): Cursor =
    if(path.isRoot)
      CirceCursor(Context(path.rootTpe), value, None, env)
    else
      DeferredCursor(path, (context, parent) => CirceCursor(context, value, Some(parent), env).success)

  override def mkCursorForMappedField(parent: Cursor, fieldContext: Context, fm: FieldMapping): Result[Cursor] =
    (fm, parent.focus) match {
      case (CirceField(_, json, _), _) =>
        CirceCursor(fieldContext, json, Some(parent), parent.env).success
      case (CursorFieldJson(_, f, _, _), _) =>
        f(parent).map(res => CirceCursor(fieldContext, focus = res, parent = Some(parent), env = parent.env))
      case _ =>
        super.mkCursorForMappedField(parent, fieldContext, fm)
    }

  sealed trait CirceFieldMapping extends FieldMapping {
    def subtree: Boolean = true
  }

  case class CirceField(fieldName: String, value: Json, hidden: Boolean = false)(implicit val pos: SourcePos) extends CirceFieldMapping

  case class CursorFieldJson(fieldName: String, f: Cursor => Result[Json], required: List[String], hidden: Boolean = false)(
    implicit val pos: SourcePos
  ) extends CirceFieldMapping

  case class CirceCursor(
    context: Context,
    focus:  Json,
    parent: Option[Cursor],
    env:    Env
  ) extends Cursor {
    def withEnv(env0: Env): Cursor = copy(env = env.add(env0))

    def mkChild(context: Context = context, focus: Json = focus): CirceCursor =
      CirceCursor(context, focus, Some(this), Env.empty)

    def isLeaf: Boolean =
      tpe.dealias match {
        case BooleanType => focus.isBoolean
        case StringType|IDType => focus.isString
        case IntType|FloatType => focus.isNumber
        case _: EnumType => focus.isString
        case _ => false
      }

    def asLeaf: Result[Json] =
      tpe.dealias match {
        case BooleanType       if focus.isBoolean => focus.success
        case StringType|IDType if focus.isString  => focus.success
        case IntType           if focus.isNumber  =>
          focus.asNumber.flatMap(_.toLong.map(Json.fromLong))
            .toResultOrError(s"Expected Int found ${focus.noSpaces}")
        case FloatType         if focus.isNumber  => focus.success
        case e: EnumType       if focus.isString  =>
          if (focus.asString.exists(e.hasValue)) focus.success
          else Result.internalError(s"Expected Enum ${e.name}, found ${focus.noSpaces}")
        case _: ScalarType     if !focus.isObject => focus.success // custom Scalar; any non-object type is fine
        case _ =>
          Result.internalError(s"Expected Scalar type, found $tpe for focus ${focus.noSpaces} at ${context.path.reverse.mkString("/")} ")
      }

    def preunique: Result[Cursor] = {
      val listTpe = tpe.nonNull.list
      if(focus.isArray)
        mkChild(context.asType(listTpe), focus).success
      else
        Result.internalError(s"Expected List type, found $focus for ${listTpe}")
    }

    def isList: Boolean = tpe.isList && focus.isArray

    def asList[C](factory: Factory[Cursor, C]): Result[C] = tpe match {
      case ListType(elemTpe) if focus.isArray =>
        focus.asArray.map(_.view.map(e => mkChild(context.asType(elemTpe), e)).to(factory))
          .toResultOrError(s"Expected List type, found $tpe for focus ${focus.noSpaces}")
      case _ =>
        Result.internalError(s"Expected List type, found $tpe for focus ${focus.noSpaces}")
    }

    def listSize: Result[Int] = tpe match {
      case ListType(_) if focus.isArray =>
        focus.asArray.map(_.size)
          .toResultOrError(s"Expected List type, found $tpe for focus ${focus.noSpaces}")
      case _ =>
        Result.internalError(s"Expected List type, found $tpe for focus ${focus.noSpaces}")
    }

    def isNullable: Boolean = tpe.isNullable

    def asNullable: Result[Option[Cursor]] = tpe match {
      case NullableType(tpe) =>
        if (focus.isNull) None.success
        else Some(mkChild(context.asType(tpe))).success
      case _ => Result.internalError(s"Expected Nullable type, found $focus for $tpe")
    }

    def isDefined: Result[Boolean] = tpe match {
      case NullableType(_) => (!focus.isNull).success
      case _ => Result.internalError(s"Expected Nullable type, found $focus for $tpe")
    }

    def narrowsTo(subtpe: TypeRef): Result[Boolean] =
      (subtpe <:< tpe &&
        ((subtpe.dealias, focus.asObject) match {
          case (nt: TypeWithFields, Some(obj)) =>
            nt.fields.forall { f =>
              f.tpe.isNullable || obj.contains(f.name)
            } && obj.keys.forall(nt.hasField)

          case _ => false
        })).success

    def narrow(subtpe: TypeRef): Result[Cursor] =
      narrowsTo(subtpe).flatMap { n =>
        if (n)
          mkChild(context.asType(subtpe)).success
        else
          Result.internalError(s"Focus ${focus} of static type $tpe cannot be narrowed to $subtpe")
      }

    def field(fieldName: String, resultName: Option[String]): Result[Cursor] = {
      val localField =
        for {
          obj <- focus.asObject
          f   <- obj(fieldName)
        } yield mkChild(context.forFieldOrAttribute(fieldName, resultName), f)

      localField.map(_.success).getOrElse(mkCursorForField(this, fieldName, resultName))
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy