Skip to content

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
    }