Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement custom media type detection #4312

Merged
merged 10 commits into from
Oct 4, 2023

Conversation

imsdu
Copy link
Contributor

@imsdu imsdu commented Sep 28, 2023

Fixes #4266

image

@imsdu imsdu marked this pull request as draft September 28, 2023 09:55
Comment on lines 46 to 59
private val defaultContentType: ContentType.Binary = ContentTypes.`application/octet-stream`

// Creating an unmarshaller defaulting to `application/octet-stream` as a content type
implicit private val um: FromEntityUnmarshaller[Multipart.FormData] =
MultipartUnmarshallers
.multipartUnmarshaller[Multipart.FormData, Multipart.FormData.BodyPart, Multipart.FormData.BodyPart.Strict](
mediaRange = `multipart/form-data`,
defaultContentType = defaultContentType,
createBodyPart = (entity, headers) => Multipart.General.BodyPart(entity, headers).toFormDataBodyPart.get,
createStreamed = (_, parts) => Multipart.FormData(parts),
createStrictBodyPart =
(entity, headers) => Multipart.General.BodyPart.Strict(entity, headers).toFormDataBodyPart.get,
createStrict = (_, parts) => Multipart.FormData.Strict(parts)
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default from akka is text/plain, aligning it to application/octet-stream as in the storage part
A bit verbose but it depends on akka there

@@ -0,0 +1,34 @@
app {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the way to run the storage in the tests so that it aligns with Delta
More readable than using system properties too

* @param isDir
* flag to decide whether or not the path is a directory
*/
def apply(path: Path, isDir: Boolean = false): ContentType =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it could be wrapped in a F[_]/ an IO but it feels like enough yak shaving and enough complexity for a single PR

customMediaType <- mediaTypeDetector.find(extension)
} yield ContentType(customMediaType, () => HttpCharsets.`UTF-8`)

bodyDefinedContentType.orElse(detectFromConfig).getOrElse(contentTypeFromAkka)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything set by the user prevails over automatic detection

/**
* Extracts the extension from the given filename
*/
def extension(filename: String): Option[String] = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allows to get rid of this apache commons io dependency

build.sbt Show resolved Hide resolved
build.sbt Show resolved Hide resolved
build.sbt Outdated
scalaTest % Test
),
cleanFiles ++= Seq(
baseDirectory.value / "permissions-fixer" / "target" / "**",
baseDirectory.value / "nexus-storage.jar"
),
Test / testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-o", "-u", "target/test-reports"),
Test / parallelExecution := false,
Universal / mappings := {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same to put back

Comment on lines 92 to 99
case part if part.name == fieldName =>
val filename = part.filename.getOrElse("file")
val contentType = detectContentType(filename, part.entity.contentType)
FileDescription(filename, contentType).runToFuture.map { desc =>
Some(desc -> part.entity)
}
case part =>
part.entity.discardBytes().future.as(None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - this chunk could be its own function

Comment on lines 75 to 86
case RejectionError(r) =>
WrappedAkkaRejection(r)
case Unmarshaller.NoContentException =>
WrappedAkkaRejection(RequestEntityExpectedRejection)
case x: UnsupportedContentTypeException =>
WrappedAkkaRejection(UnsupportedRequestContentTypeRejection(x.supported, x.actualContentType))
case x: IllegalArgumentException =>
WrappedAkkaRejection(ValidationRejection(Option(x.getMessage).getOrElse(""), Some(x)))
case x: ExceptionWithErrorInfo =>
WrappedAkkaRejection(MalformedRequestContentRejection(x.info.format(withDetail = false), x))
case x =>
WrappedAkkaRejection(MalformedRequestContentRejection(Option(x.getMessage).getOrElse(""), x))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment - could have an error handling function

Copy link
Contributor

@shinyhappydan shinyhappydan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a lot of code integrating the akka that is going over my head, but this seems fine in principle

storage/src/main/resources/app.conf Show resolved Hide resolved
response.status shouldEqual StatusCodes.Created
}
storageId2 = s"${storageId}2"
_ <- deltaClient.get[Json](s"/storages/$fullId/nxv:$storageId2", Coyote) { (json, response) =>
val expected = storageResponse(fullId, storageId2, s"$storageName/read", s"$storageName/write")
_ <- deltaClient.get[Json](s"/storages/$projectRef/nxv:$storageId2", Coyote) { (json, response) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this change

@imsdu imsdu marked this pull request as ready for review October 3, 2023 12:23
@imsdu imsdu changed the title Implement custom content type detection Implement custom media type detection Oct 3, 2023
olivergrabinski
olivergrabinski previously approved these changes Oct 3, 2023
Comment on lines 21 to 33
test("Detect overridden content type") {
val customMediaType = MediaTypes.`application/vnd.api+json`
val detector = new ContentTypeDetector(MediaTypeDetectorConfig("json" -> MediaTypes.`application/vnd.api+json`))
val expected = ContentType(customMediaType, () => `UTF-8`)
assertEquals(detector(jsonPath, isDir = false), expected)
}

test("Detect overridden content type") {
val customMediaType = MediaTypes.`application/vnd.api+json`
val detector = new ContentTypeDetector(MediaTypeDetectorConfig("json" -> MediaTypes.`application/vnd.api+json`))
val expected = ContentType(customMediaType, () => `UTF-8`)
assertEquals(detector(jsonPath, isDir = false), expected)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate?

@imsdu imsdu merged commit cefab5d into BlueBrain:master Oct 4, 2023
@imsdu imsdu deleted the 4266-change-media-type-files branch October 4, 2023 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Change the detection of media type for files
4 participants