All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sangria.ast.SourceMapper.scala Maven / Gradle / Ivy

package sangria.ast

/** Set of functions that convert a [[AstLocation GraphQL source code location]] to human-readable
  * strings.
  *
  * When rendering the results of a GraphQL document parse, it's helpful to describe where parsing
  * failed. This is the interface to that facility.
  */
trait SourceMapper {

  /** Identifier for the GraphQL document being parsed. Should be unique. */
  def id: String

  /** The GraphQL source code mapped by this object. */
  def source: String

  /** Return a description of the given location. */
  def renderLocation(location: AstLocation): String

  /** Return an indication of the line position of the given location.
    *
    * Useful for pointing to the location of a parsing error.
    *
    * @param prefix
    *   prefix to attach to the returned string
    */
  def renderLinePosition(location: AstLocation, prefix: String = ""): String
}

trait SourceMapperInput {
  def source: String
  def getLine(line: Int): String
}

/** [[sangria.ast.SourceMapper]] for a single GraphQL document. */
class DefaultSourceMapper(val id: String, val sourceMapperInput: SourceMapperInput)
    extends sangria.ast.SourceMapper {
  override lazy val source: String = sourceMapperInput.source

  override def renderLocation(location: AstLocation) =
    s"(line ${location.line}, column ${location.column})"

  override def renderLinePosition(location: AstLocation, prefix: String = ""): String =
    sourceMapperInput
      .getLine(location.line)
      .replace("\r", "") + "\n" + prefix + (" " * (location.column - 1)) + "^"
}

/** [[SourceMapper]] for potentially multiple GraphQL documents.
  *
  * Sometimes it's necessary to compose a GraphQL document from multiple component documents; this
  * class provides the corresponding `SourceMapper` to support that.
  *
  * @param id
  *   Identifier for the combined document.
  * @param delegates
  *   The component documents.
  */
class AggregateSourceMapper(val id: String, val delegates: Vector[SourceMapper])
    extends SourceMapper {
  lazy val delegateById: Map[String, SourceMapper] = delegates.iterator.map(d => d.id -> d).toMap

  override lazy val source: String = delegates.map(_.source.trim).mkString("\n\n")

  override def renderLocation(location: AstLocation): String =
    delegateById.get(location.sourceId).fold("")(sm => sm.renderLocation(location))

  override def renderLinePosition(location: AstLocation, prefix: String = ""): String =
    delegateById.get(location.sourceId).fold("")(sm => sm.renderLinePosition(location, prefix))
}

object AggregateSourceMapper {
  private[this] def expand(sm: SourceMapper): Vector[SourceMapper] = sm match {
    case agg: AggregateSourceMapper => agg.delegates.flatMap(expand)
    case m => Vector(m)
  }

  def merge(mappers: Vector[SourceMapper]): AggregateSourceMapper =
    new AggregateSourceMapper("merged", mappers.flatMap(expand))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy