Skip to content

Commit

Permalink
add metadata field
Browse files Browse the repository at this point in the history
  • Loading branch information
olivergrabinski committed Feb 23, 2024
1 parent b445faa commit 2b47d56
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import cats.syntax.all._
import ch.epfl.bluebrain.nexus.delta.kernel.error.NotARejection
import ch.epfl.bluebrain.nexus.delta.kernel.http.MediaTypeDetectorConfig
import ch.epfl.bluebrain.nexus.delta.kernel.utils.FileUtils
import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.FileRejection.{FileTooLarge, InvalidKeywords, InvalidMultipartFieldName, WrappedAkkaRejection}
import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.FileRejection
import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.FileRejection.{FileTooLarge, InvalidFileMetadata, InvalidKeywords, InvalidMultipartFieldName, WrappedAkkaRejection}
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Label
import io.circe.parser
import io.circe.generic.semiauto.deriveDecoder
import io.circe.{parser, Decoder}

import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
Expand Down Expand Up @@ -138,13 +140,14 @@ object FormDataExtractor {
case part if part.name == FileFieldName =>
val filename = part.filename.getOrElse("file")
val contentType = detectContentType(filename, part.entity.contentType)
val description = part.dispositionParams.get("description").filter(_.nonEmpty)
val name = part.dispositionParams.get("descriptiveName").filter(_.nonEmpty)

val result = for {
keywords <- extractKeywords(part)
metadata <- extractMetadata(part)
} yield {
Some(UploadedFileInformation(filename, keywords, description, name, contentType, part.entity))
Some(
UploadedFileInformation(filename, keywords, metadata.description, metadata.name, contentType, part.entity)
)
}

Future.fromTry(result.toTry)
Expand All @@ -165,6 +168,24 @@ object FormDataExtractor {
}
}

private case class FileUploadMetadata(name: Option[String], description: Option[String])
implicit private val fileUploadMetadataDecoder: Decoder[FileUploadMetadata] =
deriveDecoder[FileUploadMetadata]

private def extractMetadata(
part: Multipart.FormData.BodyPart
): Either[FileRejection, FileUploadMetadata] = {
val metadata = part.dispositionParams.get("metadata").filter(_.nonEmpty)
metadata match {
case Some(value) =>
parser
.parse(value)
.flatMap(_.as[FileUploadMetadata])
.leftMap(err => InvalidFileMetadata(err.getMessage))
case None => Right(FileUploadMetadata(None, None))
}
}

private def detectContentType(filename: String, contentTypeFromRequest: ContentType) = {
val bodyDefinedContentType = Option.when(contentTypeFromRequest != defaultContentType)(contentTypeFromRequest)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ object FileRejection {
final case class InvalidKeywords(err: String)
extends FileRejection(s"File payload contained keywords which could not be parsed: $err")

/**
* Rejection returned when attempting to create/update a file with a Multipart/Form-Data payload that contains
* invalid metadata
*/
final case class InvalidFileMetadata(err: String)
extends FileRejection(s"File payload contained metadata which could not be parsed: $err")

/**
* Rejection returned when attempting to create/update a file with a Multipart/Form-Data payload that does not
* contain a ''file'' fieldName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.AkkaSou
import ch.epfl.bluebrain.nexus.delta.sdk.syntax._
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Label
import ch.epfl.bluebrain.nexus.testkit.scalatest.ce.CatsEffectSpec
import io.circe.syntax.KeyOps
import io.circe.syntax.{EncoderOps, KeyOps}
import io.circe.{Json, JsonObject}

class FormDataExtractorSpec
Expand Down Expand Up @@ -67,19 +67,24 @@ class FormDataExtractorSpec
description: Option[String],
name: Option[String]
): Map[String, String] = {

val metadata = JsonObject(
"name" -> name.asJson,
"description" -> description.asJson
).toJson

Map.from(
filename.map("filename" -> _) ++
Option.when(keywords.nonEmpty)("keywords" -> JsonObject.fromMap(keywords).toJson.noSpaces) ++
description.map("description" -> _) ++
name.map("descriptiveName" -> _)
Option.when(!metadata.isEmpty())("metadata" -> metadata.noSpaces)
)
}

"be extracted with the default content type" in {
val entity = createEntity("file", NoContentType, Some("filename"))

val UploadedFileInformation(filename, _, _, _, contentType, contents) =
extractor(iri, entity, 179, None).accepted
extractor(iri, entity, 200, None).accepted

filename shouldEqual "filename"
contentType shouldEqual `application/octet-stream`
Expand All @@ -100,7 +105,7 @@ class FormDataExtractorSpec
val entity = createEntity("file", NoContentType, Some("file.txt"))

val UploadedFileInformation(filename, _, _, _, contentType, contents) =
extractor(iri, entity, 179, None).accepted
extractor(iri, entity, 200, None).accepted
filename shouldEqual "file.txt"
contentType shouldEqual `text/plain(UTF-8)`
consume(contents.dataBytes) shouldEqual content
Expand Down

0 comments on commit 2b47d56

Please sign in to comment.