Skip to content
This repository has been archived by the owner on Jun 12, 2024. It is now read-only.

Revert "DR2 1598 update ingest lock table with new fields" #28

Merged
merged 2 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ Their identifierName will be prefixed with id_ and used as the field name
The formatter will report all errors with a particular Dynamo row.

## Lock table
This holds a lock with an asset ID, a message ID, the parentMessage ID and the execution ID.
This holds a lock with an information object ID, a batch id and the message to be processed.
### Validation
#### Mandatory fields
These fields must always be present
* assetId (UUID)
* messageId (UUID)
* ioId (UUID)
* batchId (String)
* message (String)
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.9.9
sbt.version=1.9.6
16 changes: 6 additions & 10 deletions src/main/scala/uk/gov/nationalarchives/DynamoFormatters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ object DynamoFormatters {
}

val batchId = "batchId"
val ioId = "ioId"
val id = "id"
val message = "message"
val name = "name"
val typeField = "type"
val fileSize = "fileSize"
Expand All @@ -78,11 +80,6 @@ object DynamoFormatters {
val representationType = "representationType"
val representationSuffix = "representationSuffix"

val assetId = "assetId"
val messageId = "messageId"
val parentMessageId = "parentMessageId"
val executionId = "executionId"

given pkFormat: Typeclass[PartitionKey] = deriveDynamoFormat[PartitionKey]

enum Type:
Expand All @@ -102,10 +99,9 @@ object DynamoFormatters {
private type ValidatedField[T] = ValidatedNel[(FieldName, DynamoReadError), T]

case class LockTableValidatedFields(
assetId: ValidatedField[UUID], // We have not yet decided whether it's going to be Zref or UUID
messageId: ValidatedField[UUID],
parentMessageId: ValidatedField[Option[UUID]],
executionId: Option[String]
assetId: ValidatedField[UUID],
batchId: ValidatedField[String],
message: ValidatedField[String]
)

case class FilesTableValidatedFields(
Expand Down Expand Up @@ -193,7 +189,7 @@ object DynamoFormatters {

case class PartitionKey(id: UUID)

case class IngestLockTable(assetId: UUID, messageId: UUID, parentMessageId: Option[UUID], executionId: Option[String])
case class IngestLockTable(ioId: UUID, batchId: String, message: String)

enum FileRepresentationType:
override def toString: String = this match
Expand Down
35 changes: 8 additions & 27 deletions src/main/scala/uk/gov/nationalarchives/DynamoReadUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,12 @@ class DynamoReadUtils(folderRowAsMap: Map[String, AttributeValue]) {

private val allValidatedLockTableFields: LockTableValidatedFields = LockTableValidatedFields(
stringToScalaType[UUID](
assetId,
getPotentialStringValue(assetId),
ioId,
getPotentialStringValue(ioId),
UUID.fromString
),
stringToScalaType[UUID](
messageId,
getPotentialStringValue(messageId),
UUID.fromString
),
optionalStringToOptionalScalaType[UUID](
parentMessageId,
getPotentialStringValue(parentMessageId),
UUID.fromString
),
getPotentialStringValue(executionId)
getValidatedMandatoryFieldAsString(batchId),
getValidatedMandatoryFieldAsString(message)
)

private val allValidatedFileTableFields: FilesTableValidatedFields = FilesTableValidatedFields(
Expand Down Expand Up @@ -162,16 +153,6 @@ class DynamoReadUtils(folderRowAsMap: Map[String, AttributeValue]) {
case None => (name -> MissingProperty).invalidNel
}

private def optionalStringToOptionalScalaType[T: ClassTag](
name: String,
potentialString: Option[String],
toScalaTypeFunction: String => T
): ValidatedNel[(FieldName, TypeCoercionError), Option[T]] =
Validated
.catchOnly[Throwable](potentialString.map(toScalaTypeFunction))
.leftMap(_ => typeCoercionError[T](name, potentialString.getOrElse("")))
.toValidatedNel

private def convertListOfStringsToT[T: ClassTag](fromStringToAnotherType: String => T)(
attributeName: String,
attributes: List[AttributeValue]
Expand All @@ -183,10 +164,10 @@ class DynamoReadUtils(folderRowAsMap: Map[String, AttributeValue]) {
def readLockTableRow: Either[InvalidPropertiesError, IngestLockTable] =
(
allValidatedLockTableFields.assetId,
allValidatedLockTableFields.messageId,
allValidatedLockTableFields.parentMessageId
).mapN { (assetId, messageId, parentMessageId) =>
IngestLockTable(assetId, messageId, parentMessageId, allValidatedLockTableFields.executionId)
allValidatedLockTableFields.batchId,
allValidatedLockTableFields.message
).mapN { (assetId, batchId, message) =>
IngestLockTable(assetId, batchId, message)
}.toEither
.left
.map(InvalidPropertiesError.apply)
Expand Down
14 changes: 4 additions & 10 deletions src/main/scala/uk/gov/nationalarchives/DynamoWriteUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,10 @@ object DynamoWriteUtils {

def writeLockTable(lockTable: IngestLockTable): DynamoValue =
DynamoObject {
val optionalFields: Map[FieldName, DynamoValue] = Map(
"executionId" -> lockTable.executionId.map(id => DynamoValue.fromString(id)),
"parentMessageId" -> lockTable.parentMessageId.map(pid => DynamoValue.fromString(pid.toString))
).flatMap {
case (fieldName, Some(value)) => Map(fieldName -> value)
case _ => Map.empty
}
optionalFields ++ Map(
assetId -> DynamoValue.fromString(lockTable.assetId.toString),
messageId -> DynamoValue.fromString(lockTable.messageId.toString)
Map(
ioId -> DynamoValue.fromString(lockTable.ioId.toString),
batchId -> DynamoValue.fromString(lockTable.batchId),
message -> DynamoValue.fromString(lockTable.message)
)
}.toDynamoValue
}
111 changes: 25 additions & 86 deletions src/test/scala/uk/gov/nationalarchives/DynamoFormattersTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import org.scalatest.EitherValues
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers.*
import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1, TableFor3}
import org.scanamo
import org.scanamo.*
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
import software.amazon.awssdk.services.dynamodb.model.AttributeValue.{fromL, fromM, fromN, fromS}
import uk.gov.nationalarchives.DynamoFormatters.{*, given}
import uk.gov.nationalarchives.DynamoFormatters.{given, *}
import uk.gov.nationalarchives.DynamoFormatters.Type.*
import uk.gov.nationalarchives.DynamoFormatters.FileRepresentationType.*

Expand Down Expand Up @@ -419,103 +418,43 @@ class DynamoFormattersTest extends AnyFlatSpec with TableDrivenPropertyChecks wi
}

"lockTable read" should "read the correct fields" in {
val assetId = UUID.randomUUID()
val parentMessageId = UUID.randomUUID()
val messageId = UUID.randomUUID()
val executionId = "executionId"
val ioId = UUID.randomUUID()
val batchId = "batchId"
val message = "{}"

val input =
fromM(
Map(
"assetId" -> fromS(assetId.toString),
"parentMessageId" -> fromS(parentMessageId.toString),
"messageId" -> fromS(messageId.toString),
"executionId" -> fromS(executionId)
).asJava
)
fromM(Map("ioId" -> fromS(ioId.toString), "batchId" -> fromS(batchId), "message" -> fromS(message)).asJava)
val res = ingestLockTableFormat.read(input).value
res.assetId should equal(assetId)
res.parentMessageId should equal(Some(parentMessageId))
res.messageId should equal(messageId)
res.executionId should equal(Some(executionId))
res.ioId should equal(ioId)
res.batchId should equal(batchId)
res.message should equal(res.message)
}

"lockTable read" should "return the error type 'MissingProperty' for each of the required fields, 'assetId' and 'messageId', " +
"if they are missing but not for the optional fields 'parentMessageId' and 'executionId'" in {
val input = fromM(Map("invalidField" -> fromS(UUID.randomUUID().toString)).asJava)
val res = ingestLockTableFormat.read(input)
val expectedMissingProperties = List(assetId, messageId)
"lockTable read" should "error if the field is missing" in {
val ioId = UUID.randomUUID()

val actualMissingProperties = res.left.value.asInstanceOf[InvalidPropertiesError].errors

val expectedPropertiesAreMissing = actualMissingProperties.forall {
case (name: String, a: MissingProperty.type) => expectedMissingProperties.contains(name)
case _ => false
}
res.isLeft should be(true)
actualMissingProperties.length should equal(2)
expectedPropertiesAreMissing should be(true)

val actualMissingPropertyNames = actualMissingProperties.map { case (name, _) => name }.toList
List("parentMessageId", "executionId").foreach { optionalProperty =>
actualMissingPropertyNames.contains(optionalProperty) should equal(false)
}
}

"lockTable read" should "return the values of 'None' for the optional properties 'parentMessageId' and 'executionId' " +
"if they are missing" in {
val assetId = UUID.randomUUID()
val parentMessageId = UUID.randomUUID()
val messageId = UUID.randomUUID()
val executionId = "executionId"

val input =
fromM(
Map(
"assetId" -> fromS(assetId.toString),
"messageId" -> fromS(messageId.toString)
).asJava
)
val res = ingestLockTableFormat.read(input).value
res.assetId should equal(assetId)
res.parentMessageId should equal(None)
res.messageId should equal(messageId)
res.executionId should equal(None)
val input = fromM(Map("invalidField" -> fromS(ioId.toString)).asJava)
val res = ingestLockTableFormat.read(input)
res.isLeft should be(true)
val isMissingPropertyError = res.left.value.asInstanceOf[InvalidPropertiesError].errors.head._2 match {
case MissingProperty => true
case _ => false
}
isMissingPropertyError should be(true)
}

"lockTable write" should "write the correct fields" in {
val assetId = UUID.randomUUID()
val messageId = UUID.randomUUID()
val parentMessageId = UUID.randomUUID()
val executionId = "executionId"
val ioId = UUID.randomUUID()
val batchId = "batchId"
val message = "{}"

val attributeValueMap =
ingestLockTableFormat
.write(IngestLockTable(assetId, messageId, Some(parentMessageId), Some(executionId)))
.toAttributeValue
.m()
.asScala

attributeValueMap("assetId").s() should equal(assetId.toString)
attributeValueMap("messageId").s() should equal(messageId.toString)
attributeValueMap("parentMessageId").s() should equal(parentMessageId.toString)
attributeValueMap("executionId").s() should equal(executionId)
ingestLockTableFormat.write(IngestLockTable(ioId, batchId, message)).toAttributeValue.m().asScala
UUID.fromString(attributeValueMap("ioId").s()) should equal(ioId)
attributeValueMap("batchId").s() should equal("batchId")
attributeValueMap("message").s() should equal("{}")
}

"lockTable write" should "write only the required fields, 'assetId' and 'messageId' if the value of 'None' was given " +
"for 'parentMessageId' and 'executionId'" in {
val assetId = UUID.randomUUID()
val messageId = UUID.randomUUID()

val attributeValueMap =
ingestLockTableFormat.write(IngestLockTable(assetId, messageId, None, None)).toAttributeValue.m().asScala

attributeValueMap("assetId").s() should equal(assetId.toString)
attributeValueMap("messageId").s() should equal(messageId.toString)
attributeValueMap.get("parentMessageId").map(_.s()) should equal(None)
attributeValueMap.get("executionId").map(_.s()) should equal(None)
}

private def generateListAttributeValue(values: String*): AttributeValue =
fromL(
values.toList
Expand Down