Skip to content

Instantly share code, notes, and snippets.

@otobrglez
Created February 26, 2025 10:35
Show Gist options
  • Save otobrglez/5625ef89bb932401b071cfed05ca3741 to your computer and use it in GitHub Desktop.
Save otobrglez/5625ef89bb932401b071cfed05ca3741 to your computer and use it in GitHub Desktop.
GraphQL in Scala 3
package si.ogrodje.flow.gql
import scala.annotation.targetName
object GQL:
private type Name = String
private type Arguments = Map[Name, Value]
private val emptyArguments: Arguments = Map.empty
private type Fields = List[Field]
private val emptyFields: Fields = List.empty
extension (arguments: Arguments)
def render: String =
Option
.when(arguments.nonEmpty)(arguments.map((k, v) => s"$k: ${v.render}").mkString("(", ", ", ")"))
.getOrElse("")
enum Value:
case StringValue(value: String) extends Value
case IntValue(value: Int) extends Value
case FloatValue(value: Float) extends Value
case BooleanValue(value: Boolean) extends Value
case ListValue(value: List[Value]) extends Value
case Obj(fields: Map[Name, Value] = Map.empty) extends Value
case Literal(value: String) extends Value
case NullValue extends Value
final def render: String = this match
case StringValue(value) => s""""$value""""
case IntValue(value) => value.toString
case FloatValue(value) => value.toString
case BooleanValue(value) => value.toString
case Value.ListValue(value) => value.map(_.render).mkString("[", ", ", "]")
case Obj(value) => value.map((k, v) => s"$k: ${v.render}").mkString("{", ", ", "}")
case Literal(value) => value
case NullValue => "null"
import Value.*
final protected case class Field(
name: Name,
arguments: Arguments = emptyArguments,
fields: Fields = emptyFields
):
def render(indent: Int = 0): String =
val indentStr = " " * indent
val subFields = Option
.when(fields.nonEmpty)(fields.map(_.render(indent + 1)).mkString(" {\n", "\n", s"\n$indentStr}"))
.getOrElse("")
indentStr + name + arguments.render + subFields
def field(name: Name): Field = Field(name)
def field(name: Name, fields: Field*): Field = Field(name, fields = fields.toList)
def field(name: Name, fields: List[Field]): Field = Field(name, fields = fields)
def field(name: Name, arguments: Arguments, fields: Field*): Field = Field(name, arguments, fields.toList)
def field(name: Name, arguments: Arguments, fields: List[Field]): Field = Field(name, arguments, fields = fields)
def argument[V <: Value](name: Name, value: V): Arguments = emptyArguments ++ Map(name -> value)
def argument[V <: Value](kv: (Name, Value)*): Arguments = emptyArguments ++ kv.toMap
@targetName("argumentInt")
def argument(kv: (Name, Int)*): Arguments = emptyArguments ++ kv.map((k, v) => k -> IntValue(v))
@targetName("argumentString")
def argument(kv: (Name, String)*): Arguments = emptyArguments ++ kv.map((k, v) => k -> StringValue(v))
def arguments(kv: (Name, Value)*): Arguments = emptyArguments ++ kv.toMap
@targetName("argumentsString")
def arguments(kv: (Name, String)*): Arguments = emptyArguments ++ kv.map((k, v) => k -> StringValue(v))
@targetName("argumentsInt")
def arguments(kv: (Name, Int)*): Arguments = emptyArguments ++ kv.map((k, v) => k -> IntValue(v))
extension (field: Field) def ++(other: Field): List[Field] = List(field, other)
final class Query private (
name: Name,
arguments: Map[Name, Value] = Map.empty,
fields: List[Field] = List.empty
):
def render: String =
val fieldsPart = fields.map(_.render()).mkString("\n\n")
s"query $name${arguments.render} {\n$fieldsPart\n}"
object Query:
def apply(name: Name): Query = new Query(name)
def apply(name: Name, fields: Field*): Query = new Query(name, fields = fields.toList)
def apply(name: Name, fields: List[Field]): Query = new Query(name, fields = fields)
def apply(name: Name, arguments: Arguments, fields: Field*): Query = new Query(name, arguments, fields.toList)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment