Created
March 1, 2019 10:00
-
-
Save anthonynsimon/da07607480d1013c2201fb47a18e0176 to your computer and use it in GitHub Desktop.
Elastic Search DSL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.collection.JavaConverters._ | |
import com.fasterxml.jackson.databind.node._ | |
import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper} | |
import scala.language.implicitConversions | |
object JacksonDSL { | |
object ObjectNode { | |
def apply(props: (String, JsonNode)*): ObjectNode = { | |
val obj = new ObjectNode(JsonNodeFactory.instance) | |
props.foreach { case (k, v) => obj.set(k, v) } | |
obj | |
} | |
def parse(json: String): JsonNode = new ObjectMapper().readTree(json) | |
} | |
object ArrayNode { | |
def apply(elems: JsonNode*): ArrayNode = { | |
val arr = new ArrayNode(JsonNodeFactory.instance) | |
elems.foreach(arr.add) | |
arr | |
} | |
} | |
// Implicit conversions between basic types and their JsonNode equivalents | |
implicit def toJson(v: Int): IntNode = IntNode.valueOf(v) | |
implicit def toJson(v: Boolean): BooleanNode = BooleanNode.valueOf(v) | |
implicit def toJson(v: String): TextNode = TextNode.valueOf(v) | |
implicit def toJson(v: Double): DoubleNode = DoubleNode.valueOf(v) | |
// Extensions methods to map MissingNode to Option | |
implicit class PimpedJsonNode(val node: JsonNode) extends AnyVal { | |
def toOption: Option[JsonNode] = if (node.isMissingNode) None else Some(node) | |
def maybeAt(path: String): Option[JsonNode] = node.at(path).toOption | |
} | |
} | |
sealed trait Expr | |
case class Search(e: Expr*) extends Expr | |
case class Query(e: Expr*) extends Expr | |
case class Bool(e: Expr*) extends Expr | |
case class From(n: Int) extends Expr | |
case class Size(n: Int) extends Expr | |
case class Sort(field: String, order: String) extends Expr | |
case class Filter(e: Expr*) extends Expr | |
case class Must(e: Expr*) extends Expr | |
case class MustNot(e: Expr*) extends Expr | |
case class Should(e: Expr*) extends Expr | |
case class ShouldNot(e: Expr*) extends Expr | |
case class Match(field: String, value: String) extends Expr | |
case class StringTerm(field: String, value: String) extends Expr | |
case class IntTerm(field: String, value: Int) extends Expr | |
case class BooleanTerm(field: String, value: Boolean) extends Expr | |
case class DoubleTerm(field: String, value: Double) extends Expr | |
def asJson(expression: Expr): ObjectNode = { | |
import JacksonDSL._ | |
def mergeNodes(nodes: Seq[ObjectNode]): ObjectNode = { | |
val result = ObjectNode() | |
nodes.foreach { node => | |
node.fieldNames().asScala.foreach { field => | |
val value = node.get(field) | |
result.set(field, value) | |
} | |
} | |
result | |
} | |
expression match { | |
case search: Search => mergeNodes(search.e.map(ex => asJson(ex))) | |
case query: Query => ObjectNode( | |
"query" -> mergeNodes(query.e.map(ex => asJson(ex))) | |
) | |
case bool: Bool => ObjectNode( | |
"bool" -> mergeNodes(bool.e.map(ex => asJson(ex))) | |
) | |
case filter: Filter => ObjectNode( | |
"filter" -> mergeNodes(filter.e.map(ex => asJson(ex))) | |
) | |
case must: Must => ObjectNode( | |
"must" -> ArrayNode(must.e.map(ex => asJson(ex)): _*) | |
) | |
case mustNot: MustNot => ObjectNode( | |
"must_not" -> ArrayNode(mustNot.e.map(ex => asJson(ex)): _*) | |
) | |
case should: Should => ObjectNode( | |
"should" -> ArrayNode(should.e.map(ex => asJson(ex)): _*) | |
) | |
case shouldNot: ShouldNot => ObjectNode( | |
"should_not" -> ArrayNode(shouldNot.e.map(ex => asJson(ex)): _*) | |
) | |
case matched: Match => ObjectNode( | |
"match" -> ObjectNode( | |
matched.field -> matched.value | |
) | |
) | |
case term: StringTerm => ObjectNode( | |
"term" -> ObjectNode( | |
term.field -> term.value | |
) | |
) | |
case term: BooleanTerm => ObjectNode( | |
"term" -> ObjectNode( | |
term.field -> term.value | |
) | |
) | |
case term: DoubleTerm => ObjectNode( | |
"term" -> ObjectNode( | |
term.field -> term.value | |
) | |
) | |
case term: IntTerm => ObjectNode( | |
"term" -> ObjectNode( | |
term.field -> term.value | |
) | |
) | |
case from: From => ObjectNode("from" -> from.n) | |
case size: Size => ObjectNode("size" -> size.n) | |
case sort: Sort => ObjectNode("sort" -> ObjectNode( | |
sort.field -> ObjectNode( | |
"order" -> sort.order | |
) | |
)) | |
} | |
} | |
val query = Search( | |
From(0), | |
Size(90), | |
Sort("rating", "desc"), | |
Query( | |
Bool( | |
Must( | |
StringTerm("genre", "drama"), | |
StringTerm("locale", "en-US"), | |
), | |
MustNot( | |
BooleanTerm("featured", true) | |
) | |
) | |
), | |
) | |
asJson(query).toString |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment