Document source¶
Document source
represents an Elasticsearch document in Kotlin. Document source is
responsible for data serialization/deserialization. For example, almost all
Elasticsearch data types can be multi-valued and a mapping doesn't reflect that fact.
But in our programs we want to operate with concrete types, as we work differently with
String
or List<String>
. Also all fields in a mapping are optional that requires null
-checks
in the code. Specifying document source we can set up proper (de)serialization of underlying data.
Warning
This API is a subject to change. See more info at issue
Suppose we have following Document
:
package samples.docsource
import dev.evo.elasticmagic.doc.BaseDocSource
import dev.evo.elasticmagic.doc.BoundField
import dev.evo.elasticmagic.doc.Document
import dev.evo.elasticmagic.doc.SubDocument
class RoleDoc(field: BoundField<BaseDocSource, Nothing>) : SubDocument(field) {
val name by keyword()
val permissions by keyword()
}
object UserDoc : Document() {
val id by int()
val login by keyword()
val groups by keyword()
val roles by nested(::RoleDoc)
}
Dynamic¶
The most simple way to work with source documents is just to use DynDocSource
:
package samples.docsource.dynamic
import dev.evo.elasticmagic.doc.DynDocSource
import dev.evo.elasticmagic.doc.list
import samples.docsource.UserDoc
val root = DynDocSource {
it[UserDoc.id] = 0
it[UserDoc.login] = "root"
it[UserDoc.groups.list()] = mutableListOf("root", "wheel")
it[UserDoc.roles.list()] = mutableListOf(
DynDocSource {
it[UserDoc.roles.name] = "superuser"
it[UserDoc.roles.permissions.list()] = mutableListOf("*")
}
)
}
// Int?
val rootId = root[UserDoc.id]
// List<String?>?
val rootPermissions = root[UserDoc.roles.list()]
?.mapNotNull {
it?.get(UserDoc.roles.permissions.list())
}
?.flatten()
User defined¶
This is the recommended way. You can explicitly specify document source:
package samples.docsource.custom
import dev.evo.elasticmagic.doc.DocSource
import samples.docsource.UserDoc
// Explicit types are specified for clarity. You can totally omit them
class RoleDocSource : DocSource() {
var name: String by UserDoc.roles.name.required()
var permissions: MutableList<String> by UserDoc.roles.permissions.required().list().required()
}
class UserDocSource : DocSource() {
// id and login fields must be present
var id: Int by UserDoc.id.required()
var login: String by UserDoc.login.required()
// If groups field is missing or null default value will be used
var groups: MutableList<String> by UserDoc.groups.required().list().default { mutableListOf() }
// Optional list of a required RoleDocSource instances
var roles: MutableList<RoleDocSource>? by UserDoc.roles.source(::RoleDocSource).required().list()
}
val nobody = UserDocSource().apply {
id = 65535
login = "nobody"
}
val nobodyHasGroups = nobody.groups.isEmpty()
val root = UserDocSource().apply {
id = 0
login = "root"
groups = mutableListOf("root", "wheel")
roles = mutableListOf(
RoleDocSource().apply {
name = "superuser"
permissions = mutableListOf("*")
}
)
}
val rootId: Int = root.id
val rootPermissions: List<String>? = root.roles
?.flatMap {
it.permissions
}