202 lines
6.5 KiB
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]
|
|
}
|