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

co.block.scalajack.dynamodb.DynamoFlavor.scala Maven / Gradle / Ivy

package co.blocke.scalajack
package dynamodb

import model._
import typeadapter._
import co.blocke.scala_reflection.RType

import scala.jdk.CollectionConverters._
import com.amazonaws.services.dynamodbv2.document.Item
import com.amazonaws.services.dynamodbv2.model.{
  AttributeDefinition,
  CreateTableRequest,
  KeySchemaElement,
  KeyType,
  ProvisionedThroughput,
  ScalarAttributeType
}

case class DynamoFlavor(
  override val defaultHint:        String                           = "_hint",
  override val permissivesOk:      Boolean                          = false,
  override val customAdapters:     List[TypeAdapterFactory]         = List.empty[TypeAdapterFactory],
  override val hintMap:            Map[String, String]              = Map.empty[String, String],
  override val hintValueModifiers: Map[String, HintValueModifier]   = Map.empty[String, HintValueModifier],
  override val typeValueModifier:  HintValueModifier                = DefaultHintModifier,
  override val parseOrElseMap:     Map[Class[_], RType]             = Map.empty[Class[_], RType],
  override val enumsAsInt:         Boolean                          = false
) extends JackFlavor[Item] {

  // $COVERAGE-OFF$Not testing any of this stuff.  Either unused for Dynamo or an exact copy of thoroughly-tested JSON flavor
  override val stringifyMapKeys: Boolean = true

  def stringWrapTypeAdapterFactory[T](
      wrappedTypeAdapter: TypeAdapter[T],
      emptyStringOk:      Boolean        = true
  ): TypeAdapter[T] = ???

  def maybeStringWrapTypeAdapterFactory[T](
    wrappedTypeAdapter: TypeAdapter[T],
    emptyStringOk: Boolean = true
  ): TypeAdapter[T] = ???

  def allowPermissivePrimitives(): JackFlavor[Item] =
    this.copy(permissivesOk = true)
  def enumsAsInts(): JackFlavor[Item] = this.copy(enumsAsInt = true)
  def parseOrElse(poe: (RType, RType)*): JackFlavor[Item] =
    this.copy(parseOrElseMap = this.parseOrElseMap ++ poe.map{(p,oe) => p.infoClass->oe})
  def withAdapters(ta: TypeAdapterFactory*): JackFlavor[Item] =
    this.copy(customAdapters = this.customAdapters ++ ta.toList)
  def withDefaultHint(hint: String): JackFlavor[Item] =
    this.copy(defaultHint = hint)
  def withHints(h: (RType, String)*): JackFlavor[Item] =
    this.copy(hintMap = this.hintMap ++ h.map{(rt,hint) => rt.name->hint})
  def withHintModifiers(hm: (RType, HintValueModifier)*): JackFlavor[Item] =
    this.copy(hintValueModifiers = this.hintValueModifiers ++ hm.map{(rt,hintM) => rt.name->hintM})
  def withTypeValueModifier(tm: HintValueModifier): JackFlavor[Item] =
    this.copy(typeValueModifier = tm)

  def parse(input: Item): Parser = ???
  // $COVERAGE-ON$

  // Embedded JSON-flavored ScalaJack, as Item can read/write JSON, so this is actually the most straightforward
  // path to serialization.
  lazy val sj: JackFlavor[json.JSON] = {
    val baseSj = ScalaJack()
      .withAdapters(customAdapters: _*)
      .withHints(hintMap.map{ case (k,v) => (RType.of(Class.forName(k)),v) }.toList: _*)
      .withHintModifiers(hintValueModifiers.map{ case (k,v) => (RType.of(Class.forName(k)),v)}.toList: _*)
      .withDefaultHint(defaultHint)
      .parseOrElse(parseOrElseMap.map{ case(k,v) => (RType.of(k),v)}.toList: _*)
    baseSj.withTypeValueModifier(typeValueModifier)
  }

  private val jsonWriter = json.JsonWriter() 

  def _read[T](input: Item, typeAdapter: TypeAdapter[T]): T =
    // sj.read[T](input.toJSON.asInstanceOf[json.JSON])
    val parser = json.JsonParser(input.toJSON.asInstanceOf[json.JSON], sj)
    typeAdapter.read(parser).asInstanceOf[T]

  def _render[T](t: T, typeAdapter: TypeAdapter[T]): Item =
    val sb = StringBuilder[json.JSON]()
    typeAdapter.write(t, jsonWriter, sb)
    Item.fromJSON(sb.result().asInstanceOf[String])
    // Item.fromJSON(sj.render[T](t).asInstanceOf[String])
    /*
    def write[WIRE](
      t:      T,
      writer: Writer[WIRE],
      out:    mutable.Builder[WIRE, WIRE]): Unit
    */

  // This is Dynamo-Only.  User will have to case ScalaJack to DynamoFlavor to call this.
  // Yeah, this is a little large-ish for an inlined call, but it *MUST* be inlined for [T]
  // to be known/visible, otherwise it will be (incorrectly) interpreted as Any!
  inline def createTableRequest[T](provisionedThroughput: ProvisionedThroughput): CreateTableRequest = {
    val (optionalTableName, keys, className) = taCache.typeAdapterOf[T] match {
      case ta: classes.ClassTypeAdapterBase[_] => (ta.dbCollectionName, ta.dbKeys, ta.info.name)
    }
    val tableName = optionalTableName.getOrElse(
      throw new java.lang.IllegalStateException(
        s"Class ${className} must be annotated with @Collection to specify a table name."
      )
    )

    val cleanKeys = keys.map(_.asInstanceOf[ClassFieldMember[_, _]])

    if (cleanKeys.isEmpty)
      throw new java.lang.IllegalStateException(
        s"Class ${className} must define at least a primary key with @DBKey."
      )

    val attrDetail = cleanKeys.zipWithIndex.collect {
      case (key, idx) if idx == 0 =>
        (
          new AttributeDefinition(key.name, getAttrType(key)),
          new KeySchemaElement(key.name, KeyType.HASH)
        )
      case (key, idx) if idx == 1 =>
        (
          new AttributeDefinition(key.name, getAttrType(key)),
          new KeySchemaElement(key.name, KeyType.RANGE)
        )
    }

    new CreateTableRequest(
      attrDetail.map(_._1).asJava,
      tableName,
      attrDetail.map(_._2).asJava,
      provisionedThroughput
    )
  }

  private def getAttrType(key: ClassFieldMember[_, _]) =
    if (key.valueTypeAdapter.isStringish)
      ScalarAttributeType.S
    else
      ScalarAttributeType.N
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy