sangria.renderer.QueryRenderer.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sangria-core_2.13 Show documentation
Show all versions of sangria-core_2.13 Show documentation
Scala GraphQL implementation
The newest version!
package sangria.renderer
import sangria.ast._
import sangria.util.StringUtil.{escapeBlockString, escapeString, linesIterator}
object QueryRenderer {
val Pretty: QueryRendererConfig = QueryRendererConfig(
indentLevel = " ",
lineBreak = "\n",
separator = " ",
mandatorySeparator = " ",
mandatoryLineBreak = "\n",
definitionSeparator = "\n\n",
inputFieldSeparator = ", ",
inputListSeparator = ",",
formatInputValues = false,
renderComments = true,
formatBlockStrings = true
)
val PrettyInput: QueryRendererConfig =
Pretty.copy(inputFieldSeparator = "\n", formatInputValues = true, renderComments = true)
val Compact: QueryRendererConfig = QueryRendererConfig(
indentLevel = "",
lineBreak = "",
separator = "",
mandatorySeparator = " ",
mandatoryLineBreak = " ",
definitionSeparator = "\n",
inputFieldSeparator = ",",
inputListSeparator = ",",
formatInputValues = false,
renderComments = false,
formatBlockStrings = false
)
def renderSelections(
sels: Vector[Selection],
tc: WithTrailingComments,
indent: Indent,
config: QueryRendererConfig): String =
if (sels.nonEmpty) {
val rendered = sels.iterator.zipWithIndex
.map { case (sel, idx) =>
val prev = if (idx == 0) None else Some(sels(idx - 1))
val next = if (idx == sels.size - 1) None else Some(sels(idx + 1))
val trailingNext =
for {
n <- next
c <- n.comments.headOption
cp <- c.location
sp <- sel.location
if cp.line == sp.line
} yield c
val trailing =
trailingNext.orElse(for {
c <- tc.trailingComments.headOption
cp <- c.location
sp <- sel.location
if cp.line == sp.line
} yield c)
(if (idx != 0 && shouldRenderComment(sel, prev, config)) config.lineBreak
else "") + renderNode(sel, config, indent.inc, prev = prev) +
trailing.fold("")(c => renderIndividualComment(c, " ", config))
}
.mkString(config.mandatoryLineBreak)
"{" +
config.lineBreak +
rendered +
renderTrailingComment(tc, sels.lastOption, indent.inc, config) +
trailingLineBreak(tc, config) +
indent.str +
"}"
} else ""
def renderFieldDefinitions(
fields: Vector[FieldDefinition],
tc: WithTrailingComments,
indent: Indent,
config: QueryRendererConfig,
frontSep: Boolean = false): String =
if (fields.nonEmpty) {
val rendered = fields.iterator.zipWithIndex
.map { case (field, idx) =>
val prev = if (idx == 0) None else Some(fields(idx - 1))
val next = if (idx == fields.size - 1) None else Some(fields(idx + 1))
val trailingNext =
for {
n <- next
c <- n.description.fold(n.comments)(_.comments).headOption
cp <- c.location
sp <- field.location
if cp.line == sp.line
} yield c
val trailing =
trailingNext.orElse(for {
c <- tc.trailingComments.headOption
cp <- c.location
sp <- field.location
if cp.line == sp.line
} yield c)
(if (idx != 0 && (shouldRenderComment(field, prev, config) || shouldRenderDescription(
field))) config.lineBreak
else "") +
renderNode(field, config, indent.inc, prev = prev) +
trailing.fold("")(c => renderIndividualComment(c, " ", config))
}
.mkString(config.mandatoryLineBreak)
(if (frontSep) config.separator else "") +
"{" +
config.lineBreak +
rendered +
renderTrailingComment(tc, fields.lastOption, indent.inc, config) +
trailingLineBreak(tc, config) +
indent.str +
"}"
} else ""
def renderInputFieldDefinitions(
fields: Vector[InputValueDefinition],
tc: WithTrailingComments,
indent: Indent,
config: QueryRendererConfig,
frontSep: Boolean = false): String =
if (fields.nonEmpty) {
(if (frontSep) config.separator else "") +
"{" +
config.lineBreak +
renderInputObjectFieldDefs(fields, tc, indent, config) +
renderTrailingComment(tc, fields.lastOption, indent.inc, config) +
trailingLineBreak(tc, config) +
indent.str +
"}"
} else ""
def renderEnumValues(
values: Vector[EnumValueDefinition],
tc: WithTrailingComments,
indent: Indent,
config: QueryRendererConfig,
frontSep: Boolean = false): String =
if (values.nonEmpty) {
val renderedValues = values.iterator.zipWithIndex
.map { case (value, idx) =>
val prev = if (idx == 0) None else Some(values(idx - 1))
val next = if (idx == values.size - 1) None else Some(values(idx + 1))
val trailingNext =
for {
n <- next
c <- n.description.fold(n.comments)(_.comments).headOption
cp <- c.location
sp <- value.location
if cp.line == sp.line
} yield c
val trailing =
trailingNext.orElse(for {
c <- tc.trailingComments.headOption
cp <- c.location
sp <- value.location
if cp.line == sp.line
} yield c)
(if (idx != 0 && (shouldRenderComment(value, prev, config) || shouldRenderDescription(
value))) config.lineBreak
else "") +
renderNode(value, config, indent.inc, prev = prev) +
trailing.fold("")(c => renderIndividualComment(c, " ", config))
}
.mkString(config.mandatoryLineBreak)
(if (frontSep) config.separator else "") +
"{" +
config.lineBreak +
renderedValues +
renderTrailingComment(tc, values.lastOption, indent.inc, config) +
trailingLineBreak(tc, config) +
indent.str +
"}"
} else ""
def renderOperationTypeDefinitions(
ops: Vector[OperationTypeDefinition],
tc: WithTrailingComments,
indent: Indent,
config: QueryRendererConfig,
frontSep: Boolean = false): String =
if (ops.nonEmpty) {
val renderedOps = ops.iterator.zipWithIndex
.map { case (op, idx) =>
(if (idx != 0 && shouldRenderComment(op, None, config)) config.lineBreak else "") +
renderNode(op, config, indent.inc)
}
.mkString(config.mandatoryLineBreak)
(if (frontSep) config.separator else "") +
"{" +
config.lineBreak +
renderedOps +
renderTrailingComment(tc, None, indent.inc, config) +
trailingLineBreak(tc, config) +
indent.str + "}"
} else ""
def renderDirs(
dirs: Vector[Directive],
config: QueryRendererConfig,
indent: Indent,
frontSep: Boolean = false,
withSep: Boolean = true): String =
(if (dirs.nonEmpty && frontSep && withSep) config.separator else "") +
dirs.iterator.map(renderNode(_, config, indent.zero)).mkString(config.separator) +
(if (dirs.nonEmpty && !frontSep && withSep) config.separator else "")
def renderArgs(
args: Vector[Argument],
indent: Indent,
config: QueryRendererConfig,
withSep: Boolean = true): String =
if (args.nonEmpty) {
val argsRendered = args.iterator.zipWithIndex
.map { case (a, idx) =>
(if (idx != 0 && shouldRenderComment(a, None, config)) config.lineBreak else "") +
(if (shouldRenderComment(a, None, config)) config.mandatoryLineBreak
else if (idx != 0) config.separator
else "") +
renderNode(
a,
config,
if (shouldRenderComment(a, None, config)) indent.inc else indent.zero)
}
"(" + argsRendered.mkString(",") + ")" + (if (withSep) config.separator else "")
} else ""
def renderInputValueDefs(
args: Vector[InputValueDefinition],
indent: Indent,
config: QueryRendererConfig,
withSep: Boolean = true): String =
if (args.nonEmpty) {
val argsRendered = args.iterator.zipWithIndex
.map { case (a, idx) =>
(if (idx != 0 && (shouldRenderComment(a, None, config) || shouldRenderDescription(a)))
config.lineBreak
else "") +
(if (shouldRenderComment(a, None, config) || shouldRenderDescription(a))
config.mandatoryLineBreak
else if (idx != 0) config.separator
else "") +
renderNode(
a,
config,
if (shouldRenderComment(a, None, config) || shouldRenderDescription(a)) indent.inc
else indent.zero)
}
"(" + argsRendered.mkString(",") + ")" + (if (withSep) config.separator else "")
} else ""
def renderVarDefs(
vars: Vector[VariableDefinition],
indent: Indent,
config: QueryRendererConfig,
withSep: Boolean = true): String =
if (vars.nonEmpty) {
val varsRendered = vars.iterator.zipWithIndex
.map { case (v, idx) =>
(if (idx != 0 && shouldRenderComment(v, None, config)) config.lineBreak else "") +
(if (shouldRenderComment(v, None, config)) config.mandatoryLineBreak
else if (idx != 0) config.separator
else "") +
renderNode(
v,
config,
if (shouldRenderComment(v, None, config)) indent + 2 else indent.zero)
}
"(" + varsRendered.mkString(",") + ")" + (if (withSep) config.separator else "")
} else ""
def renderInputObjectFieldDefs(
fields: Vector[InputValueDefinition],
tc: WithTrailingComments,
indent: Indent,
config: QueryRendererConfig): String = {
val fieldsRendered = fields.iterator.zipWithIndex
.map { case (f, idx) =>
val prev = if (idx == 0) None else Some(fields(idx - 1))
val next = if (idx == fields.size - 1) None else Some(fields(idx + 1))
val trailingNext =
for {
n <- next
c <- n.description.fold(n.comments)(_.comments).headOption
cp <- c.location
sp <- f.location
if cp.line == sp.line
} yield c
val trailing =
trailingNext.orElse(for {
c <- tc.trailingComments.headOption
cp <- c.location
sp <- f.location
if cp.line == sp.line
} yield c)
(if (idx != 0 && (shouldRenderComment(f, prev, config) || shouldRenderDescription(f)))
config.lineBreak
else "") +
renderNode(f, config, indent.inc, prev = prev) +
trailing.fold("")(c => renderIndividualComment(c, " ", config))
}
fieldsRendered.mkString(config.mandatoryLineBreak)
}
def renderInterfaces(
interfaces: Vector[NamedType],
config: QueryRendererConfig,
indent: Indent,
frontSep: Boolean = false,
withSep: Boolean = true): String =
if (interfaces.nonEmpty)
(if (frontSep) config.mandatorySeparator else "") +
"implements" + config.mandatorySeparator +
interfaces.iterator
.map(renderNode(_, config, indent.zero))
.mkString(config.separator + "&" + config.separator) +
(if (withSep) config.separator else "")
else ""
def renderOpType(operationType: OperationType): String = operationType match {
case OperationType.Query => "query"
case OperationType.Mutation => "mutation"
case OperationType.Subscription => "subscription"
}
def actualComments(node: WithComments, prev: Option[AstNode]): Vector[Comment] = {
val ignoreFirst = for {
ls <- prev
p <- ls.location
c <- node.comments.headOption
cp <- c.location
} yield cp.line == p.line
ignoreFirst match {
case Some(true) => node.comments.tail
case _ => node.comments
}
}
def shouldRenderComment(
node: WithComments,
prev: Option[AstNode],
config: QueryRendererConfig): Boolean =
config.renderComments && actualComments(node, prev).nonEmpty
def shouldRenderDescription(node: WithDescription): Boolean =
node.description.fold(false)(_.value.trim.nonEmpty)
def shouldRenderComment(node: WithTrailingComments, config: QueryRendererConfig): Boolean =
config.renderComments && node.trailingComments.nonEmpty
def shouldRenderComment(comments: Vector[Comment], config: QueryRendererConfig): Boolean =
config.renderComments && comments.nonEmpty
private def startsWithWhitespace(text: String) =
text.charAt(0).isWhitespace
def renderIndividualComment(node: Comment, indent: String, config: QueryRendererConfig): String =
indent + "#" + (if (node.text.trim.nonEmpty && !startsWithWhitespace(node.text)) " "
else "") + node.text
def renderDescription(
node: WithDescription,
prev: Option[AstNode],
indent: Indent,
config: QueryRendererConfig): String =
node.description match {
case Some(description) =>
renderComment(description, prev, indent, config) +
indent.str + renderStringValue(description, indent, config, extraIndent = false) +
config.mandatoryLineBreak
case None => ""
}
def renderComment(
node: WithComments,
prev: Option[AstNode],
indent: Indent,
config: QueryRendererConfig): String = {
val comments = actualComments(node, prev)
if (shouldRenderComment(comments, config)) {
val lines = renderCommentLines(comments, node.location, indent, config)
lines.mkString("", config.mandatoryLineBreak, config.mandatoryLineBreak)
} else ""
}
def renderCommentLines(
comments: Vector[Comment],
nodePos: Option[AstLocation],
indent: Indent,
config: QueryRendererConfig): Seq[String] = {
val nodeLine =
nodePos.map(_.line).orElse(comments.last.location.map(_.line + 1)).fold(1)(identity)
comments
.foldRight((nodeLine, Vector.empty[String])) { case (c, (lastLine, acc)) =>
val currLine = c.location.fold(lastLine - 1)(_.line)
val diffLines = lastLine - currLine
val fill = if (diffLines > 1) config.lineBreak else ""
currLine -> ((renderIndividualComment(c, indent.str, config) + fill) +: acc)
}
._2
}
def renderTrailingComment(
node: WithTrailingComments,
lastSelection: Option[AstNode],
indent: Indent,
config: QueryRendererConfig): String = {
val ignoreFirst = for {
ls <- lastSelection
p <- ls.location
c <- node.trailingComments.headOption
cp <- c.location
} yield cp.line == p.line
val comments = ignoreFirst match {
case Some(true) => node.trailingComments.tail
case _ => node.trailingComments
}
if (shouldRenderComment(comments, config)) {
val lines = renderCommentLines(comments, None, indent, config)
lines.mkString(config.lineBreak + config.mandatoryLineBreak, config.mandatoryLineBreak, "")
} else ""
}
def renderInputComment(node: WithComments, indent: Indent, config: QueryRendererConfig): String =
if (config.formatInputValues && shouldRenderComment(node, None, config))
renderComment(node, None, indent, config) + indent.str
else ""
def renderStringValue(
node: StringValue,
indent: Indent,
config: QueryRendererConfig,
extraIndent: Boolean = true): String =
if (node.block && config.formatBlockStrings)
renderBlockString(node, indent, config, extraIndent)
else renderNonBlockString(node, indent, config)
def renderNonBlockString(node: StringValue, indent: Indent, config: QueryRendererConfig) =
s""""${escapeString(node.value)}""""
def renderBlockString(
node: StringValue,
indent: Indent,
config: QueryRendererConfig,
extraIndent: Boolean = true): String =
if (node.value.trim.nonEmpty) {
val ind = if (extraIndent) indent.incForce.str else indent.strForce
val lines = linesIterator(escapeBlockString(node.value)).map { line =>
if (line.isEmpty) line // do not output lines with only whitespaces inside
else ind + line
}
lines.mkString(
"\"\"\"" + config.mandatoryLineBreak,
config.mandatoryLineBreak,
config.mandatoryLineBreak + ind + "\"\"\"")
} else "\"\""
def render(node: AstNode, config: QueryRendererConfig = Pretty, indentLevel: Int = 0): String =
renderNode(node, config, Indent(config, indentLevel, indentLevel))
def renderPretty(node: AstNode): String = node match {
case _: Value => render(node, PrettyInput)
case _ => render(node, Pretty)
}
def renderCompact(node: AstNode): String = render(node, Compact)
def renderNode(
node: AstNode,
config: QueryRendererConfig,
indent: Indent,
prefix: Option[String] = None,
prev: Option[AstNode] = None): String =
node match {
case d @ Document(defs, _, _, _) =>
defs.iterator
.map(renderNode(_, config, indent))
.mkString(config.definitionSeparator) +
renderTrailingComment(d, None, indent, config)
case d @ InputDocument(defs, _, _, _) =>
defs.iterator
.map(renderNode(_, config, indent))
.mkString(config.definitionSeparator) +
renderTrailingComment(d, None, indent, config)
case op @ OperationDefinition(OperationType.Query, None, vars, dirs, sels, _, _, _)
if vars.isEmpty && dirs.isEmpty =>
renderComment(op, prev, indent, config) + indent.str + renderSelections(
sels,
op,
indent,
config)
case op @ OperationDefinition(opType, name, vars, dirs, sels, _, _, _) =>
renderComment(op, prev, indent, config) +
indent.str + renderOpType(opType) + config.mandatorySeparator +
name.getOrElse("") +
renderVarDefs(vars, indent, config, withSep = false) +
config.separator +
renderDirs(dirs, config, indent) +
renderSelections(sels, op, indent, config)
case fd @ FragmentDefinition(name, typeCondition, dirs, sels, vars, _, _, _) =>
renderComment(fd, prev, indent, config) +
indent.str + "fragment" + config.mandatorySeparator + name + renderVarDefs(
vars,
indent,
config,
withSep = false) + config.mandatorySeparator + "on" +
config.mandatorySeparator + typeCondition.name + config.separator +
renderDirs(dirs, config, indent) +
renderSelections(sels, fd, indent, config)
case vd @ VariableDefinition(name, tpe, defaultValue, dirs, _, _) =>
renderComment(vd, prev, indent, config) +
indent.str + "$" + name + ":" + config.separator +
renderNode(tpe, config, indent.zero) +
defaultValue.fold("")(v =>
config.separator + "=" + config.separator + renderNode(v, config, indent.zero)) +
renderDirs(dirs, config, indent, frontSep = true)
case NotNullType(ofType, _) =>
renderNode(ofType, config, indent.zero) + "!"
case ListType(ofType, _) =>
"[" + renderNode(ofType, config, indent.zero) + "]"
case NamedType(name, _) =>
name
case f @ Field(alias, name, args, dirs, sels, _, _, _) =>
renderComment(f, prev, indent, config) +
indent.str + alias.fold("")(_ + ":" + config.separator) + name +
renderArgs(args, indent, config, withSep = false) +
(if (dirs.nonEmpty || sels.nonEmpty) config.separator else "") +
renderDirs(dirs, config, indent, withSep = sels.nonEmpty) +
renderSelections(sels, f, indent, config)
case fs @ FragmentSpread(name, dirs, _, _) =>
renderComment(fs, prev, indent, config) +
indent.str + "..." + name + renderDirs(dirs, config, indent, frontSep = true)
case ifr @ InlineFragment(typeCondition, dirs, sels, _, _, _) =>
renderComment(ifr, prev, indent, config) +
indent.str + "..." + config.mandatorySeparator + typeCondition.fold("")(
"on" + config.mandatorySeparator + _.name) + config.separator +
renderDirs(dirs, config, indent) +
renderSelections(sels, ifr, indent, config)
case Directive(name, args, _, _) =>
indent.str + "@" + name + renderArgs(
args,
indent,
config.copy(renderComments = false),
withSep = false)
case a @ Argument(name, value, _, _) =>
renderComment(a, prev, indent, config) +
indent.str + name + ":" + config.separator + renderNode(value, config, indent.zero)
case v @ IntValue(value, _, _) => renderInputComment(v, indent, config) + value
case v @ BigIntValue(value, _, _) => renderInputComment(v, indent, config) + value
case v @ FloatValue(value, _, _) => renderInputComment(v, indent, config) + value
case v @ BigDecimalValue(value, _, _) => renderInputComment(v, indent, config) + value
case v @ BooleanValue(value, _, _) => renderInputComment(v, indent, config) + value
case v @ NullValue(_, _) => renderInputComment(v, indent, config) + "null"
case v @ EnumValue(value, _, _) => renderInputComment(v, indent, config) + value
case v @ StringValue(_, _, _, _, _) =>
renderInputComment(v, indent, config) + renderStringValue(v, indent, config)
case v @ ListValue(value, _, _) =>
def addIdent(v: Value) = v match {
case _: ObjectValue => false
case _ => true
}
def renderValue(v: Value, idx: Int) =
if (config.formatInputValues && shouldRenderComment(v, None, config))
(if (idx != 0) config.lineBreak else "") +
config.lineBreak +
renderNode(v, config, indent + (if (addIdent(v)) 1 else 0))
else
(if (idx != 0) config.separator else "") + renderNode(v, config, indent)
renderInputComment(v, indent, config) +
"[" + value.iterator.zipWithIndex
.map { case (v, idx) => renderValue(v, idx) }
.mkString(config.inputListSeparator) + "]"
case v @ ObjectValue(value, _, _) =>
renderInputComment(v, indent, config) +
"{" + inputLineBreak(config) +
value.iterator.zipWithIndex
.map { case (v, idx) =>
(if (idx != 0 && config.formatInputValues && shouldRenderComment(v, None, config))
config.lineBreak
else "") + renderNode(v, config, inputFieldIndent(config, indent))
}
.mkString(config.inputFieldSeparator) +
inputLineBreak(config) + inputIndent(config, indent) + "}"
case VariableValue(name, _, _) => indent.str + "$" + name
case v @ ObjectField(name, value, _, _) =>
val rendered =
if (config.formatInputValues && shouldRenderComment(value, None, config))
config.lineBreak + renderNode(value, config, indent.inc)
else
config.separator + renderNode(value, config, indent)
(if (config.formatInputValues) renderComment(v, prev, indent, config) else "") +
indent.str + name + ":" + rendered
case c @ Comment(_, _) => renderIndividualComment(c, indent.str, config)
case std @ ScalarTypeDefinition(name, dirs, description, _, _) =>
renderDescription(std, prev, indent, config) +
renderComment(std, description.orElse(prev), indent, config) +
indent.str + "scalar" + config.mandatorySeparator + name +
renderDirs(dirs, config, indent, frontSep = true)
case otd @ ObjectTypeDefinition(name, interfaces, fields, dirs, description, _, _, _) =>
renderDescription(otd, prev, indent, config) +
renderComment(otd, description.orElse(prev), indent, config) +
indent.str + prefix.getOrElse("") + "type" + config.mandatorySeparator + name +
config.mandatorySeparator +
renderInterfaces(interfaces, config, indent) +
renderDirs(dirs, config, indent, withSep = fields.nonEmpty) +
renderFieldDefinitions(fields, otd, indent, config)
case itd @ InputObjectTypeDefinition(name, fields, dirs, description, _, _, _) =>
renderDescription(itd, prev, indent, config) +
renderComment(itd, description.orElse(prev), indent, config) +
indent.str + "input" + config.mandatorySeparator + name +
renderDirs(dirs, config, indent, frontSep = true) +
renderInputFieldDefinitions(fields, itd, indent, config, frontSep = true)
case itd @ InterfaceTypeDefinition(name, fields, interfaces, dirs, description, _, _, _) =>
renderDescription(itd, prev, indent, config) +
renderComment(itd, description.orElse(prev), indent, config) +
indent.str + "interface" + config.mandatorySeparator + name +
(if (interfaces.nonEmpty) config.mandatorySeparator else "") +
renderInterfaces(interfaces, config, indent, withSep = false) +
(if (dirs.nonEmpty) config.separator else "") +
renderDirs(dirs, config, indent, withSep = false) +
(if (fields.nonEmpty) config.separator else "") +
renderFieldDefinitions(fields, itd, indent, config)
case utd @ UnionTypeDefinition(name, types, dirs, description, _, _) =>
val typesString =
if (types.nonEmpty)
config.separator + "=" + config.separator +
types.iterator
.map(renderNode(_, config, indent.zero))
.mkString(config.separator + "|" + config.separator)
else
""
renderDescription(utd, prev, indent, config) +
renderComment(utd, description.orElse(prev), indent, config) +
indent.str + "union" + config.mandatorySeparator + name +
renderDirs(dirs, config, indent, frontSep = true) +
typesString
case etd @ EnumTypeDefinition(name, values, dirs, description, _, _, _) =>
renderDescription(etd, prev, indent, config) +
renderComment(etd, description.orElse(prev), indent, config) +
indent.str + "enum" + config.mandatorySeparator + name +
renderDirs(dirs, config, indent, frontSep = true) +
renderEnumValues(values, etd, indent, config, frontSep = true)
case evd @ EnumValueDefinition(name, dirs, description, _, _) =>
renderDescription(evd, prev, indent, config) +
renderComment(evd, description.orElse(prev), indent, config) +
indent.str + name +
renderDirs(dirs, config, indent, frontSep = true)
case fd @ FieldDefinition(name, fieldType, args, dirs, description, _, _) =>
renderDescription(fd, prev, indent, config) +
renderComment(fd, description.orElse(prev), indent, config) +
indent.str + name +
renderInputValueDefs(args, indent, config, withSep = false) +
":" + config.separator + renderNode(fieldType, config, indent.zero) +
renderDirs(dirs, config, indent, frontSep = true)
case ivd @ InputValueDefinition(name, valueType, default, dirs, description, _, _) =>
renderDescription(ivd, prev, indent, config) +
renderComment(ivd, description.orElse(prev), indent, config) +
indent.str + name + ":" + config.separator + renderNode(valueType, config, indent.zero) +
default.fold("")(d =>
config.separator + "=" + config.separator + renderNode(d, config, indent.zero)) +
renderDirs(dirs, config, indent, frontSep = true)
case ted @ ObjectTypeExtensionDefinition(name, interfaces, fields, dirs, _, _, _) =>
renderComment(ted, prev, indent, config) +
indent.str + prefix.getOrElse(
"") + "extend" + config.mandatorySeparator + "type" + config.mandatorySeparator + name +
config.mandatorySeparator +
renderInterfaces(interfaces, config, indent) +
renderDirs(dirs, config, indent, withSep = fields.nonEmpty) +
renderFieldDefinitions(fields, ted, indent, config)
case ext @ InterfaceTypeExtensionDefinition(name, interfaces, fields, dirs, _, _, _) =>
renderComment(ext, prev, indent, config) +
indent.str + "extend" + config.mandatorySeparator + "interface" + config.mandatorySeparator + name +
(if (interfaces.nonEmpty) config.mandatorySeparator else "") +
renderInterfaces(interfaces, config, indent, withSep = false) +
(if (dirs.nonEmpty) config.separator else "") +
renderDirs(dirs, config, indent, withSep = false) +
(if (fields.nonEmpty) config.separator else "") +
renderFieldDefinitions(fields, ext, indent, config)
case ext @ UnionTypeExtensionDefinition(name, types, dirs, _, _) =>
val typesString =
if (types.nonEmpty)
config.separator + "=" + config.separator +
types.iterator
.map(renderNode(_, config, indent.zero))
.mkString(config.separator + "|" + config.separator)
else
""
renderComment(ext, prev, indent, config) +
indent.str + "extend" + config.mandatorySeparator + "union" + config.mandatorySeparator + name +
renderDirs(dirs, config, indent, frontSep = true) +
typesString
case ext @ InputObjectTypeExtensionDefinition(name, fields, dirs, _, _, _) =>
renderComment(ext, prev, indent, config) +
indent.str + "extend" + config.mandatorySeparator + "input" + config.mandatorySeparator + name +
renderDirs(dirs, config, indent, frontSep = true) +
renderInputFieldDefinitions(fields, ext, indent, config, frontSep = true)
case ext @ EnumTypeExtensionDefinition(name, values, dirs, _, _, _) =>
renderComment(ext, prev, indent, config) +
indent.str + "extend" + config.mandatorySeparator + "enum" + config.mandatorySeparator + name +
renderDirs(dirs, config, indent, frontSep = true) +
renderEnumValues(values, ext, indent, config, frontSep = true)
case ext @ ScalarTypeExtensionDefinition(name, dirs, _, _) =>
renderComment(ext, prev, indent, config) +
indent.str + "extend" + config.mandatorySeparator + "scalar" + config.mandatorySeparator + name +
renderDirs(dirs, config, indent, frontSep = true)
case ext @ SchemaExtensionDefinition(ops, dirs, _, _, _) =>
renderComment(ext, prev, indent, config) +
indent.str + "extend" + config.mandatorySeparator + "schema" +
renderDirs(dirs, config, indent, frontSep = true) +
renderOperationTypeDefinitions(ops, ext, indent, config, frontSep = true)
case dd @ DirectiveDefinition(name, args, locations, description, rep, _, _) =>
val locsRendered = locations.iterator.zipWithIndex
.map { case (l, idx) =>
(if (idx != 0 && shouldRenderComment(l, None, config)) config.lineBreak else "") +
(if (shouldRenderComment(l, None, config)) config.lineBreak
else if (idx != 0) config.separator
else "") +
renderNode(
l,
config,
if (shouldRenderComment(l, None, config)) indent.inc else indent.zero)
}
renderDescription(dd, prev, indent, config) +
renderComment(dd, description.orElse(prev), indent, config) +
indent.str + "directive" + config.separator + "@" + name +
renderInputValueDefs(args, indent, config) + (if (args.isEmpty) config.mandatorySeparator
else "") +
(if (rep) "repeatable" + config.mandatorySeparator else "") +
"on" + (if (shouldRenderComment(locations.head, None, config)) ""
else config.mandatorySeparator) +
locsRendered.mkString(config.separator + "|")
case dl @ DirectiveLocation(name, _, _) =>
renderComment(dl, prev, indent, config) + indent.str + name
case sd @ SchemaDefinition(ops, dirs, description, _, _, _) =>
renderDescription(sd, prev, indent, config) +
renderComment(sd, description.orElse(prev), indent, config) +
indent.str + "schema" + config.separator +
renderDirs(dirs, config, indent) +
renderOperationTypeDefinitions(ops, sd, indent, config)
case otd @ OperationTypeDefinition(op, tpe, _, _) =>
renderComment(otd, prev, indent, config) +
indent.str + renderOpType(op) + ":" + config.separator + renderNode(
tpe,
config,
indent.zero)
}
private def trailingLineBreak(tc: WithTrailingComments, config: QueryRendererConfig) =
if (shouldRenderComment(tc, config)) config.mandatoryLineBreak else config.lineBreak
def inputLineBreak(config: QueryRendererConfig): String =
if (config.formatInputValues) config.lineBreak
else ""
def inputFieldIndent(config: QueryRendererConfig, indent: Indent): Indent =
if (config.formatInputValues) indent.inc
else indent.zero
def inputIndent(config: QueryRendererConfig, indent: Indent): String =
if (config.formatInputValues) indent.str
else ""
}
case class Indent(config: QueryRendererConfig, level: Int, prevLevel: Int) {
lazy val str: String = config.indentLevel * level
lazy val strPrev: String = config.indentLevel * prevLevel
def strForce: String = if (level > 0) str else strPrev
lazy val zero: Indent = copy(level = 0, prevLevel = if (level == 0) prevLevel else level)
def +(l: Int): Indent = copy(level = level + l, prevLevel = level)
def -(l: Int): Indent = {
val newLevel = level - l
if (newLevel >= 0) this
else copy(level = level + l, prevLevel = level)
}
def inc: Indent = this + 1
def dec: Indent = this - 1
def incForce: Indent =
if (level > 0) this + 1 else copy(level = prevLevel + 1, prevLevel = prevLevel)
}
case class QueryRendererConfig(
indentLevel: String,
lineBreak: String,
mandatorySeparator: String,
mandatoryLineBreak: String,
separator: String,
definitionSeparator: String,
inputFieldSeparator: String,
inputListSeparator: String,
formatInputValues: Boolean,
formatBlockStrings: Boolean,
renderComments: Boolean)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy