org.http4k.lens.multipartForm.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of http4k-multipart Show documentation
Show all versions of http4k-multipart Show documentation
Http4k multipart form support
package org.http4k.lens
import org.http4k.core.Body
import org.http4k.core.ContentType
import org.http4k.core.ContentType.Companion.MULTIPART_FORM_DATA
import org.http4k.core.ContentType.Companion.MultipartFormWithBoundary
import org.http4k.core.HttpMessage
import org.http4k.core.MultipartEntity
import org.http4k.core.MultipartFormBody
import org.http4k.core.MultipartFormBody.Companion.DEFAULT_DISK_THRESHOLD
import org.http4k.core.with
import org.http4k.lens.ContentNegotiation.Companion.Strict
import org.http4k.lens.Header.CONTENT_TYPE
import org.http4k.lens.Validator.Ignore
import org.http4k.multipart.DiskLocation
import java.io.Closeable
import java.util.UUID
data class MultipartForm(
val fields: Map> = emptyMap(),
val files: Map> = emptyMap(),
val errors: List = emptyList(),
val onClose: List = emptyList()
) : Closeable {
override fun close() {
files.values.flatten().forEach(MultipartFormFile::close)
onClose.forEach(Closeable::close)
}
operator fun plus(kv: Pair) =
copy(fields = fields + (kv.first to fields.getOrDefault(kv.first, emptyList()) + MultipartFormField(kv.second)))
@JvmName("plusField")
operator fun plus(kv: Pair) =
copy(fields = fields + (kv.first to fields.getOrDefault(kv.first, emptyList()) + kv.second))
@JvmName("plusFile")
operator fun plus(kv: Pair) =
copy(files = files + (kv.first to files.getOrDefault(kv.first, emptyList()) + kv.second))
fun minusField(name: String) = copy(fields = fields - name)
fun minusFile(name: String) = copy(files = files - name)
override fun equals(other: Any?): Boolean {
if (other !is MultipartForm) return false
if (fields != other.fields) return false
if (files != other.files) return false
if (errors != other.errors) return false
return true
}
override fun hashCode(): Int {
var result = fields.hashCode()
result = 31 * result + files.hashCode()
result = 31 * result + errors.hashCode()
return result
}
}
val MULTIPART_BOUNDARY = UUID.randomUUID().toString()
/**
* Convenience function to write the MultipartForm to the message body and set the content type.
*/
fun R.multipartForm(t: MultipartForm): R = with(Body.multipartForm(Ignore).toLens() of t)
fun Body.Companion.multipartForm(
validator: Validator,
vararg parts: Lens,
defaultBoundary: String = MULTIPART_BOUNDARY,
diskThreshold: Int = DEFAULT_DISK_THRESHOLD,
contentTypeFn: (String) -> ContentType = ::MultipartFormWithBoundary,
getDiskLocation: () -> DiskLocation = { DiskLocation.Temp() }
): BiDiBodyLensSpec =
BiDiBodyLensSpec(parts.map { it.meta }, MULTIPART_FORM_DATA,
LensGet { _, target ->
listOf(MultipartFormBody.from(target, diskThreshold, getDiskLocation()).apply {
Strict(contentTypeFn(boundary), CONTENT_TYPE(target))
})
},
LensSet { _: String, values: List, target: HttpMessage ->
values.fold(target) { a, b ->
a.body(b)
.with(CONTENT_TYPE of contentTypeFn(defaultBoundary))
}
})
.map({ it.toMultipartForm() }, { it.toMultipartFormBody(defaultBoundary) })
.map({ it.copy(errors = validator(it, parts.toList())) }, { it.copy(errors = validator(it, parts.toList())) })
internal fun Body.toMultipartForm(): MultipartForm = (this as MultipartFormBody).let {
it.formParts.fold(MultipartForm(onClose = listOf(this))) { memo, next ->
when (next) {
is MultipartEntity.File -> memo + (next.name to next.file)
is MultipartEntity.Field -> memo + (next.name to next.value)
}
}
}
internal fun MultipartForm.toMultipartFormBody(boundary: String): MultipartFormBody {
val withFields = fields.toList()
.fold(MultipartFormBody(boundary = boundary)) { body, (name, values) ->
values.fold(body) { bodyMemo, fieldValue ->
bodyMemo + (name to fieldValue)
}
}
return files.toList()
.fold(withFields) { body, (name, values) ->
values.fold(body) { bodyMemo, file ->
bodyMemo + (name to file)
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy