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

org.coursera.common.stringkey.StringKeyFormat.scala Maven / Gradle / Ivy

/*
 * Copyright 2016 Coursera Inc.
 *
 * 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 org.coursera.common.stringkey

import java.nio.ByteBuffer
import java.util.Base64
import java.util.UUID

import org.coursera.common.collection.Enum
import org.coursera.common.collection.EnumSymbol
import org.joda.time.DateTime
import org.joda.time.Instant

import scala.annotation.implicitNotFound
import scala.reflect.ClassTag
import scala.util.Try
import scala.collection.immutable

/**
 * Conversion from [[T]] to [[StringKey]] for use in datastores, URLs, etc. The format must
 * be stable. Changes must be backwards- and forwards-compatible so the string form of the key
 * can be stored in databases and passed on the wire by different client versions.
 *
 * These conversions must be invertible; that is, for `val t: T`, `reads(writes(t)) == Some(t)`.
 */
@implicitNotFound("Implicit `StringKeyFormat[${T}]` required. See `StringKeyFormat` for examples.")
trait StringKeyFormat[T] {

  /**
   * Attempt to parse `key` and create a `T`. Returns `None` on failure.
   */
  def reads(key: StringKey): Option[T]

  /**
   * Converts `t` to string form, wrapped in `StringKey`.
   */
  def writes(t: T): StringKey

}

object StringKeyFormat extends CommonStringKeyFormats {

  implicit val stringKeyStringKeyFormat: StringKeyFormat[StringKey] = apply(Some(_), identity)

  def apply[T](from: StringKey => Option[T], to: T => StringKey): StringKeyFormat[T] = {
    new StringKeyFormat[T] {
      def reads(key: StringKey) = from(key)
      def writes(t: T) = to(t)
    }
  }

  /**
   * Format [[T]] by first converting it to [[U]].
   */
  def delegateFormat[T, U](
      from: U => Option[T],
      to: T => U)
      (implicit otherFormat: StringKeyFormat[U]): StringKeyFormat[T] = {
    StringKeyFormat[T](key => otherFormat.reads(key).flatMap(from), t => otherFormat.writes(to(t)))
  }

  /**
   * Conveniently construct a format for a `case class` type.
   *
   * For example:
   * {{{
   *   case class StorageKey(distributionKey: String, sortKey: String)
   *
   *   object StorageKey {
   *     implicit val stringKeyFormat: StringKeyFormat[StorageKey] =
   *       StringKeyFormat.caseClassFormat((apply _).tupled, unapply)
   *   }
   * }}}
   */
  def caseClassFormat[T, U](
      apply: U => T,
      unapply: T => Option[U])
      (implicit otherFormat: StringKeyFormat[U]): StringKeyFormat[T] = {
    delegateFormat[T, U](apply.andThen(Some.apply), unapply.andThen(_.get))
  }

  def enumerationFormat(enumeration: Enumeration): StringKeyFormat[enumeration.Value] = {
    StringKeyFormat[enumeration.Value](
      k => Try(enumeration.withName(k.key)).toOption,
      v => StringKey(v.toString))
  }

  def enumFormat[SymbolType <: EnumSymbol](enumeration: Enum[SymbolType]):
    StringKeyFormat[SymbolType] = {
    StringKeyFormat[SymbolType](
      k => Try(enumeration.withName(k.key)).toOption,
      v => StringKey(v.name))
  }

  /**
   * Conveniently construct a format for an empty class.
   *
   * This should NEVER be used for non-empty case classes.
   *
   * For example:
   * {{{
   *   case object Marker {
   *     implicit val stringKeyFormat: StringKeyFormat[Marker.type] =
   *       StringKeyFormat.emptyFormat("marker", Marker)
   *   }
   * }}}
   */
  def emptyFormat[T](key: String, canonical: => T): StringKeyFormat[T] = {
    StringKeyFormat(
      _.asOpt[String].filter(_ == key).map(_ => canonical),
      _ => StringKey(key))
  }

  /**
   * Serializes [[T]] with a prefix so its string form is easily identifiable.
   *
   * For example:
   * {{{
   *   case class AuthenticatedUserId(id: Int)
   *
   *   object AuthenticatedUserId {
   *     implicit val stringKeyFormat: StringKeyFormat[AuthenticatedUserId] = {
   *       StringKeyFormat.prefixFormat(
   *         "authenticatedUser",
   *         StringKeyFormat.caseClass((apply _).tupled, unapply))
   *     }
   *   }
   * }}}
   * This format will convert `AuthenticatedUserId(1)` to `"authenticatedUser~1"`.
   */
  def prefixFormat[T](prefix: String, originalFormat: StringKeyFormat[T]): StringKeyFormat[T] = {
    implicit val implicitOriginalFormat = originalFormat
    StringKeyFormat(
      stringKey => stringKey.asOpt[(String, T)] match {
        case Some((`prefix`, key)) => Some(key)
        case _ => None
      },
      key => StringKey.toStringKey((prefix, key)))
  }

  /**
   * Useful for defining a StringKeyFormat for empty case classes or case objects based on some
   * constant string. Should NEVER be used for non-empty case classes.
   *
   * Given a `typeName` and a `canonical` representation of the object, will map
   * StringKey(typeName) <==> `canonical`
   */
  def typedEmptyStringFormat[T](
      typeName: String,
      canonical: => T): StringKeyFormat[T] = {
    StringKeyFormat(
      _.asOpt[String].filter(_ == typeName).map(_ => canonical),
      _ => StringKey(typeName))
  }

  /**
   * Useful for defining a StringKeyFormat for case classes that inherit from a common parent
   * trait.
   */
  def typedCaseFormat[T, U](
      typeName: String,
      apply: U => T,
      unapply: T => Option[U])
      (implicit otherFormat: StringKeyFormat[U]): StringKeyFormat[T] = {
    prefixFormat(typeName, caseClassFormat(apply, unapply))
  }

  def unimplementedFormat[T]: StringKeyFormat[T] = {
    StringKeyFormat(
      _ => None,
      _ => throw new UnsupportedOperationException("Writes not implemented"))
  }

  object Implicits {

    implicit class OrFormat[T](baseFormat: StringKeyFormat[T]) {

      def orFormat[U <: T](
          implicit uFormat: StringKeyFormat[U],
          classTag: ClassTag[U]): StringKeyFormat[T] = {

        def reads(stringKey: StringKey): Option[T] = {
          stringKey.asOpt[U].orElse(baseFormat.reads(stringKey))
        }

        def writes(obj: T): StringKey = obj match {
          case u: U => StringKey.toStringKey(u)
          case _ => baseFormat.writes(obj)
        }

        StringKeyFormat(reads, writes)
      }
    }
  }

}

sealed trait CommonStringKeyFormats extends DefaultTupleFormats {

  implicit val stringFormat: StringKeyFormat[String] = PrimitiveFormat(identity, identity)

  implicit val intFormat: StringKeyFormat[Int] = PrimitiveFormat(_.toInt)

  implicit val longFormat: StringKeyFormat[Long] = PrimitiveFormat(_.toLong)

  implicit val booleanFormat: StringKeyFormat[Boolean] = PrimitiveFormat(_.toBoolean)

  implicit val doubleFormat: StringKeyFormat[Double] = PrimitiveFormat(_.toDouble)

  implicit val floatFormat: StringKeyFormat[Float] = PrimitiveFormat(_.toFloat)

  implicit val shortFormat: StringKeyFormat[Short] = PrimitiveFormat(_.toShort)

  implicit val uuidFormat: StringKeyFormat[UUID] = UuidFormat

  implicit val dateTimeFormat: StringKeyFormat[DateTime] = {
    StringKeyFormat.delegateFormat[DateTime, Long](
      millis => Some(new DateTime(millis)), _.getMillis)
  }

  implicit val instantFormat: StringKeyFormat[Instant] =
    StringKeyFormat.delegateFormat[Instant, Long](millis => Some(new Instant(millis)), _.getMillis)

  /**
   * Defines a StringKeyFormat for an immutable Sequence. WARNING: SLIGHTLY INCORRECT.
   *
   * In particular, this seqFormat does not correctly handle the case when an element can be
   * serialized to the empty string.
   */
  implicit def seqFormat[T](implicit format: StringKeyFormat[T]):
    StringKeyFormat[immutable.Seq[T]] = SeqFormat[T](",")

  /**
   * Defines a StringKeyFormat for an immutable Set. WARNING: SLIGHTLY INCORRECT.
   *
   * In particular, this setFormat does not correctly handle the case when an element can be
   * serialized to the empty string.
   */
  implicit def setFormat[T](implicit format: StringKeyFormat[T]):
    StringKeyFormat[Set[T]] = SetFormat[T](",")

}

private case class PrimitiveFormat[T](from: String => T, to: T => String = (t: T) => t.toString)
  extends StringKeyFormat[T] {

  override def reads(key: StringKey): Option[T] = {
    Try(Some(from(key.key))).recover {
      case _: NumberFormatException | _: IllegalArgumentException => None
    }.get
  }

  override def writes(t: T): StringKey = StringKey(to(t))
}

private case object UuidFormat extends StringKeyFormat[UUID] {

  override def reads(key: StringKey): Option[UUID] = {
    Try {
      val bytes = Base64.getUrlDecoder.decode(key.key)
      require(bytes.length == 16, "Incorrect length")

      val byteBuffer = ByteBuffer.wrap(bytes)
      val msb = byteBuffer.getLong
      val lsb = byteBuffer.getLong

      new UUID(msb, lsb)
    }.toOption
  }

  override def writes(uuid: UUID): StringKey = {
    val bytes = ByteBuffer.allocate(16)
      .putLong(uuid.getMostSignificantBits)
      .putLong(uuid.getLeastSignificantBits)
      .array()
    StringKey(Base64.getUrlEncoder.withoutPadding().encodeToString(bytes))
  }

}


/**
 * Modified from [[TupleFormats.Tuple2Format]].
 */
private case class SeqFormat[T](separator: String)(implicit format: StringKeyFormat[T])
  extends StringKeyFormat[immutable.Seq[T]] {

  private[this] val splitRegex = s"(? None
        case item => format.reads(StringKey(unescapeSeparator(item)))
      }.toList

      if (items.forall(_.nonEmpty)) {
        Some(items.flatten)
      } else {
        None
      }
    }
  }

  override def writes(seq: immutable.Seq[T]): StringKey = {
    val string = seq.map { item =>
      escapeSeparator(format.writes(item).key)
    }.mkString(",")

    StringKey(string)
  }
}

/**
 * Represents sets in a canonical manner by ascii-sorting the string representation of the elements.
 *
 * Similar to [[SeqFormat]]
 */
private case class SetFormat[T](separator: String)(implicit format: StringKeyFormat[T])
  extends StringKeyFormat[Set[T]] {
  private[this] val splitRegex = s"(? None
        case item: String => format.reads(StringKey(unescapeSeparator(item)))
      }.toList

      if (items.forall(_.nonEmpty)) {
        Some(items.flatten.toSet)
      } else {
        None
      }
    }
  }

  override def writes(set: Set[T]): StringKey = {
    val string = set.map { item =>
      escapeSeparator(format.writes(item).key)
    }.toList.sorted.mkString(",")

    StringKey(string)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy