the-algorithm/tweetypie/common/src/scala/com/twitter/tweetypie/storage/TweetStorageClient.scala

202 lines
6.5 KiB
Scala

package com.twitter.tweetypie.storage
import com.twitter.stitch.Stitch
import com.twitter.tweetypie.storage.Response.TweetResponse
import com.twitter.tweetypie.thriftscala.Tweet
import com.twitter.util.Future
/**
* Interface for reading and writing tweet data in Manhattan
*/
trait TweetStorageClient {
import TweetStorageClient._
def addTweet: AddTweet
def deleteAdditionalFields: DeleteAdditionalFields
def getTweet: GetTweet
def getStoredTweet: GetStoredTweet
def getDeletedTweets: GetDeletedTweets
def undelete: Undelete
def updateTweet: UpdateTweet
def scrub: Scrub
def softDelete: SoftDelete
def bounceDelete: BounceDelete
def hardDeleteTweet: HardDeleteTweet
def ping: Ping
}
object TweetStorageClient {
type GetTweet = TweetId => Stitch[GetTweet.Response]
object GetTweet {
sealed trait Response
object Response {
case class Found(tweet: Tweet) extends Response
object NotFound extends Response
object Deleted extends Response
// On BounceDeleted, provide the full Tweet so that implementations
// (i.e. ManhattanTweetStorageClient) don't not need to be aware of the specific tweet
// fields required by callers for proper processing of bounced deleted tweets.
case class BounceDeleted(tweet: Tweet) extends Response
}
}
type GetStoredTweet = TweetId => Stitch[GetStoredTweet.Response]
object GetStoredTweet {
sealed abstract class Error(val message: String) {
override def toString: String = message
}
object Error {
case object TweetIsCorrupt extends Error("stored tweet data is corrupt and cannot be decoded")
case object ScrubbedFieldsPresent
extends Error("stored tweet fields that should be scrubbed are still present")
case object TweetFieldsMissingOrInvalid
extends Error("expected tweet fields are missing or contain invalid values")
case object TweetShouldBeHardDeleted
extends Error("stored tweet that should be hard deleted is still present")
}
sealed trait Response
object Response {
sealed trait StoredTweetMetadata {
def state: Option[TweetStateRecord]
def allStates: Seq[TweetStateRecord]
def scrubbedFields: Set[FieldId]
}
sealed trait StoredTweetErrors {
def errs: Seq[Error]
}
/**
* Tweet data was found, possibly state records and/or scrubbed field records.
*/
sealed trait FoundAny extends Response with StoredTweetMetadata {
def tweet: Tweet
}
object FoundAny {
def unapply(
response: Response
): Option[
(Tweet, Option[TweetStateRecord], Seq[TweetStateRecord], Set[FieldId], Seq[Error])
] =
response match {
case f: FoundWithErrors =>
Some((f.tweet, f.state, f.allStates, f.scrubbedFields, f.errs))
case f: FoundAny => Some((f.tweet, f.state, f.allStates, f.scrubbedFields, Seq.empty))
case _ => None
}
}
/**
* No records for this tweet id were found in storage
*/
case class NotFound(id: TweetId) extends Response
/**
* Data related to the Tweet id was found but could not be loaded successfully. The
* errs array contains details of the problems.
*/
case class Failed(
id: TweetId,
state: Option[TweetStateRecord],
allStates: Seq[TweetStateRecord],
scrubbedFields: Set[FieldId],
errs: Seq[Error],
) extends Response
with StoredTweetMetadata
with StoredTweetErrors
/**
* No Tweet data was found, and the most recent state record found is HardDeleted
*/
case class HardDeleted(
id: TweetId,
state: Option[TweetStateRecord.HardDeleted],
allStates: Seq[TweetStateRecord],
scrubbedFields: Set[FieldId],
) extends Response
with StoredTweetMetadata
/**
* Tweet data was found, and the most recent state record found, if any, is not
* any form of deletion record.
*/
case class Found(
tweet: Tweet,
state: Option[TweetStateRecord],
allStates: Seq[TweetStateRecord],
scrubbedFields: Set[FieldId],
) extends FoundAny
/**
* Tweet data was found, and the most recent state record found indicates deletion.
*/
case class FoundDeleted(
tweet: Tweet,
state: Option[TweetStateRecord],
allStates: Seq[TweetStateRecord],
scrubbedFields: Set[FieldId],
) extends FoundAny
/**
* Tweet data was found, however errors were detected in the stored data. Required
* fields may be missing from the Tweet struct (e.g. CoreData), stored fields that
* should be scrubbed remain present, or Tweets that should be hard-deleted remain
* in storage. The errs array contains details of the problems.
*/
case class FoundWithErrors(
tweet: Tweet,
state: Option[TweetStateRecord],
allStates: Seq[TweetStateRecord],
scrubbedFields: Set[FieldId],
errs: Seq[Error],
) extends FoundAny
with StoredTweetErrors
}
}
type HardDeleteTweet = TweetId => Stitch[HardDeleteTweet.Response]
type SoftDelete = TweetId => Stitch[Unit]
type BounceDelete = TweetId => Stitch[Unit]
object HardDeleteTweet {
sealed trait Response
object Response {
case class Deleted(deletedAtMillis: Option[Long], createdAtMillis: Option[Long])
extends Response
case class NotDeleted(id: TweetId, ineligibleLKey: Option[TweetKey.LKey])
extends Throwable
with Response
}
}
type Undelete = TweetId => Stitch[Undelete.Response]
object Undelete {
case class Response(
code: UndeleteResponseCode,
tweet: Option[Tweet] = None,
createdAtMillis: Option[Long] = None,
archivedAtMillis: Option[Long] = None)
sealed trait UndeleteResponseCode
object UndeleteResponseCode {
object Success extends UndeleteResponseCode
object BackupNotFound extends UndeleteResponseCode
object NotCreated extends UndeleteResponseCode
}
}
type AddTweet = Tweet => Stitch[Unit]
type UpdateTweet = (Tweet, Seq[Field]) => Stitch[TweetResponse]
type GetDeletedTweets = Seq[TweetId] => Stitch[Seq[DeletedTweetResponse]]
type DeleteAdditionalFields = (Seq[TweetId], Seq[Field]) => Stitch[Seq[TweetResponse]]
type Scrub = (Seq[TweetId], Seq[Field]) => Stitch[Unit]
type Ping = () => Future[Unit]
}