Skip to content

Commit

Permalink
Inline JSON encoders with companion objects
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathonherbert committed Jul 19, 2024
1 parent 06e364d commit 8a0e4a6
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 116 deletions.
4 changes: 1 addition & 3 deletions src/main/scala/Handler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.amazonaws.services.lambda.runtime.events.{
APIGatewayProxyRequestEvent,
APIGatewayProxyResponseEvent
}
import io.circe.generic.auto._
import io.circe.syntax._
import util.Logging

Expand All @@ -21,8 +20,7 @@ class Handler
APIGatewayProxyRequestEvent,
APIGatewayProxyResponseEvent
]
with Logging
with QueryJson {
with Logging {
private implicit val ec: scala.concurrent.ExecutionContext =
scala.concurrent.ExecutionContext.global

Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/HttpServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import scala.util.Try
import cql.lang.{Cql, Typeahead, TypeaheadHelpersCapi}
import com.gu.contentapi.client.GuardianContentClient

object HttpServer extends QueryJson {
object HttpServer {
val guardianContentClient = new GuardianContentClient("test")
val typeaheadHelpers = new TypeaheadHelpersCapi(guardianContentClient)
val typeahead = new Typeahead(
Expand Down
99 changes: 0 additions & 99 deletions src/main/scala/QueryJson.scala

This file was deleted.

77 changes: 77 additions & 0 deletions src/main/scala/lang/Ast.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,93 @@
package cql.lang

import io.circe.Encoder
import io.circe.syntax._
import io.circe.Json

trait Query

object QueryList {
implicit val encoder: Encoder[QueryList] = Encoder.instance { list =>
val arr = list.exprs.map {
case q: QueryBinary => q.asJson
case q: QueryField => q.asJson
case q: QueryOutputModifier => q.asJson
}
Json.obj("type" -> "QueryList".asJson, "content" -> Json.arr(arr*))
}
}
case class QueryList(
exprs: List[QueryBinary | QueryField | QueryOutputModifier]
) extends Query

object QueryBinary {
implicit val encoder: Encoder[QueryBinary] = Encoder.instance { queryBinary =>
Json.obj(
"type" -> "QueryBinary".asJson,
("left", queryBinary.left.asJson),
("right", queryBinary.right.asJson)
)
}
}
case class QueryBinary(
left: QueryContent,
right: Option[(Token, QueryContent)] = None
)

object QueryContent {
implicit val encoder: Encoder[QueryContent] = Encoder.instance {
queryContent =>
val content = queryContent.content match {
case q: QueryStr => q.asJson
case q: QueryBinary => q.asJson
case q: QueryGroup => q.asJson
}

content.deepMerge(Json.obj("type" -> "QueryContent".asJson))
}
}

case class QueryContent(content: QueryStr | QueryBinary | QueryGroup)

object QueryGroup {
implicit val encoder: Encoder[QueryGroup] = Encoder.instance { group =>
group.content.asJson.deepMerge(Json.obj("type" -> "QueryGroup".asJson))
}
}

case class QueryGroup(content: QueryBinary)

object QueryStr {
implicit val encoder: Encoder[QueryStr] = Encoder.instance { queryStr =>
Json.obj(
"type" -> "QueryStr".asJson,
"searchExpr" -> queryStr.searchExpr.asJson
)
}
}

case class QueryStr(searchExpr: String) extends Query
object QueryField {
implicit val encoder: Encoder[QueryField] = Encoder.instance { queryField =>
Json.obj(
"type" -> "QueryField".asJson,
"key" -> queryField.key.asJson,
"value" -> queryField.value.asJson
)
}
}

case class QueryField(key: Token, value: Option[Token]) extends Query

object QueryOutputModifier {
implicit val encoder: Encoder[QueryOutputModifier] =
Encoder.instance { queryField =>
Json.obj(
"type" -> "QueryOutputModifier".asJson,
"key" -> queryField.key.asJson,
"value" -> queryField.value.asJson
)
}
}

case class QueryOutputModifier(key: Token, value: Option[Token]) extends Query
26 changes: 21 additions & 5 deletions src/main/scala/lang/Cql.scala
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
package cql.lang

import scala.util.{Failure, Success, Try}
import io.circe.generic.semiauto.*

import scala.concurrent.Future
import io.circe.generic.semiauto.deriveEncoder
import io.circe.Encoder

case class CqlError(message: String, position: Option[Int] = None)

object CqlError {
implicit val typeaheadSuggestions: Encoder[CqlError] =
deriveEncoder[CqlError]
}

case class CqlResult(
tokens: List[Token],
ast: Option[QueryList],
queryResult: Option[String],
// Map from tokenType to a map of literals and their suggestions.
// Avoiding TokenType as type to avoid serialisation shenanigans in prototype.
suggestions: List[TypeaheadSuggestion],
error: Option[CqlError] = None
error: Option[CqlError]
)

object CqlResult {
implicit val cqlResultEncoder: Encoder[CqlResult] = deriveEncoder[CqlResult]
}

class Cql(typeahead: Typeahead):
implicit val ec: scala.concurrent.ExecutionContext =
scala.concurrent.ExecutionContext.global
Expand All @@ -31,7 +40,13 @@ class Cql(typeahead: Typeahead):
typeahead.getSuggestions(expr).map { suggestions =>
Try { CapiQueryString.build(expr) } match
case Success(capiQueryStr) =>
CqlResult(tokens, Some(expr), Some(capiQueryStr), suggestions)
CqlResult(
tokens,
Some(expr),
Some(capiQueryStr),
suggestions,
None
)
case Failure(e: Throwable) =>
CqlResult(
tokens,
Expand All @@ -43,7 +58,8 @@ class Cql(typeahead: Typeahead):
}
case Failure(e) =>
val error = e match {
case ParseError(position, message) => CqlError(message, Some(position))
case ParseError(position, message) =>
CqlError(message, Some(position))
case e: Throwable => CqlError(e.getMessage)
}

Expand Down
15 changes: 15 additions & 0 deletions src/main/scala/lang/Token.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package cql.lang

import io.circe.Encoder
import io.circe.Json
import io.circe.syntax._

enum TokenType:
// Single-character tokens.
case PLUS, COLON, AT, LEFT_BRACKET, RIGHT_BRACKET,
Expand All @@ -24,3 +28,14 @@ object Token:
"AND" -> TokenType.AND,
"OR" -> TokenType.OR
)

implicit val tokenEncoder: Encoder[Token] = Encoder.instance { token =>
Json.obj(
"type" -> "Token".asJson,
"tokenType" -> token.tokenType.toString.asJson,
"lexeme" -> token.lexeme.asJson,
"start" -> token.start.asJson,
"end" -> token.end.asJson,
"literal" -> token.literal.asJson
)
}
24 changes: 24 additions & 0 deletions src/main/scala/lang/Typeahead.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package cql.lang

import scala.concurrent.Future
import concurrent.ExecutionContext.Implicits.global
import io.circe.Encoder
import io.circe.generic.semiauto.deriveEncoder

case class TypeaheadSuggestion(
from: Int,
Expand All @@ -13,20 +15,42 @@ case class TypeaheadSuggestion(
suggestions: Suggestions
)

object TypeaheadSuggestion {
implicit val encoder: Encoder[TypeaheadSuggestion] =
deriveEncoder[TypeaheadSuggestion]
}

sealed trait Suggestions

case class TextSuggestion(suggestions: List[TextSuggestionOption])
extends Suggestions

object TextSuggestion {
implicit val encoder: Encoder[TextSuggestion] =
deriveEncoder[TextSuggestion]

}

case class TextSuggestionOption(
label: String,
value: String,
description: String
)

object TextSuggestionOption {

implicit val encoder: Encoder[TextSuggestionOption] =
deriveEncoder[TextSuggestionOption]
}

case class DateSuggestion(validFrom: Option[String], validTo: Option[String])
extends Suggestions

object DateSuggestion {
implicit val encoder: Encoder[DateSuggestion] =
deriveEncoder[DateSuggestion]
}

type TypeaheadResolver = (String => Future[List[TextSuggestionOption]]) |
List[TextSuggestionOption]

Expand Down
12 changes: 10 additions & 2 deletions src/test/scala/lang/ScannerTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ class ScannerTest extends BaseTest {
assert(tokens === expectedTokens)
}

it("should yield a query field value token when a query meta value is incomplete") {
it(
"should yield a query field value token when a query meta value is incomplete"
) {
val scanner = new Scanner("""example +tag:""")
val tokens = scanner.scanTokens
val expectedTokens = List(
Expand All @@ -107,7 +109,13 @@ class ScannerTest extends BaseTest {
val scanner = new Scanner("""@show-fields:all""")
val tokens = scanner.scanTokens
val expectedTokens = List(
Token(TokenType.QUERY_OUTPUT_MODIFIER_KEY, "@show-fields", Some("show-fields"), 0, 11),
Token(
TokenType.QUERY_OUTPUT_MODIFIER_KEY,
"@show-fields",
Some("show-fields"),
0,
11
),
Token(
TokenType.QUERY_VALUE,
":all",
Expand Down
Loading

0 comments on commit 8a0e4a6

Please sign in to comment.