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

sangria.parser.QueryParser.scala Maven / Gradle / Ivy

The newest version!
package sangria.parser

import org.parboiled2.CharPredicate.{Digit19, HexDigit}
import org.parboiled2._
import sangria.ast

import scala.util.{Failure, Success, Try}

private[parser] sealed trait Tokens extends StringBuilding with PositionTracking {
  this: Parser with Ignored =>

  protected[this] def Ellipsis: Rule0 = rule(quiet(str("...") ~ IgnoredNoComment.*))

  private[this] val NameFirstChar = CharPredicate.Alpha ++ '_'

  private[this] val NameChar = NameFirstChar ++ CharPredicate.Digit

  protected[this] def NameStrict: Rule1[String] = rule(
    capture(NameFirstChar ~ NameChar.*) ~ IgnoredNoComment.*)

  protected[this] def Name: Rule1[String] = rule(Ignored.* ~ NameStrict)

  protected[this] def NumberValue = rule {
    atomic(Comments ~ trackPos ~ IntegerValuePart ~ FloatValuePart.? ~ IgnoredNoComment.*) ~>
      ((comment, location, intPart, floatPart) =>
        floatPart
          .map(f => ast.BigDecimalValue(BigDecimal(intPart + f), comment, location))
          .getOrElse(ast.BigIntValue(BigInt(intPart), comment, location)))
  }

  private[this] def FloatValuePart = rule(
    atomic(capture(FractionalPart ~ ExponentPart.? | ExponentPart)))

  private[this] def FractionalPart = rule('.' ~ Digit.+)

  private[this] def IntegerValuePart = rule(capture(NegativeSign.? ~ IntegerPart))

  private[this] def IntegerPart = rule(ch('0') | NonZeroDigit ~ Digit.*)

  private[this] def ExponentPart = rule(ExponentIndicator ~ Sign.? ~ Digit.+)

  private[this] def ExponentIndicator = rule(ch('e') | ch('E'))

  private[this] def Sign = rule(ch('-') | '+')

  private[this] val NegativeSign = '-'

  private[this] val NonZeroDigit = Digit19

  private[this] def Digit = rule(ch('0') | NonZeroDigit)

  protected[this] def StringValue: Rule1[ast.StringValue] = rule(
    BlockStringValue | NonBlockStringValue)

  private[this] def BlockStringValue = rule {
    Comments ~ trackPos ~ BlockString ~ clearSB() ~ BlockStringCharacters ~ BlockString ~ push(
      sb.toString) ~ IgnoredNoComment.* ~>
      ((comment, location, s) =>
        ast.StringValue(Lexical.blockStringValue(s), true, Some(s), comment, location))
  }

  private[this] def BlockStringCharacters = rule((BlockStringCharacter | BlockStringEscapedChar).*)

  private[this] def BlockString = rule(str("\"\"\""))

  private[this] def QuotedBlockString = rule(str("\\\"\"\""))

  private[this] def BlockStringCharacter = rule {
    !(QuotedBlockString | BlockString) ~ ((CRLF | LineTerminator) ~ trackNewLine | ANY) ~ appendSB()
  }

  private[this] def BlockStringEscapedChar = rule {
    QuotedBlockString ~ appendSB("\"\"\"")
  }

  private[this] def NormalCharacter = rule(!(QuoteBackslash | LineTerminator) ~ ANY ~ appendSB())

  private[this] def NonBlockStringValue = rule {
    Comments ~ trackPos ~ '"' ~ clearSB() ~ Characters ~ '"' ~ push(
      sb.toString) ~ IgnoredNoComment.* ~>
      ((comment, location, s) => ast.StringValue(s, false, None, comment, location))
  }

  private[this] def Characters = rule((NormalCharacter | '\\' ~ EscapedChar).*)

  private[this] val QuoteBackslash = CharPredicate("\"\\")

  private[this] def EscapedChar = rule {
    QuoteBackslash ~ appendSB() |
      'b' ~ appendSB('\b') |
      'f' ~ appendSB('\f') |
      'n' ~ appendSB('\n') |
      'r' ~ appendSB('\r') |
      't' ~ appendSB('\t') |
      Unicode ~> { code => sb.append(code.asInstanceOf[Char]); () }
  }

  private[this] def Unicode = rule('u' ~ capture(4.times(HexDigit)) ~> (Integer.parseInt(_, 16)))

  protected[this] def Keyword(s: String): Rule0 = rule(
    atomic(Ignored.* ~ s ~ !NameChar ~ IgnoredNoComment.*))
}

/** Mix-in that defines GraphQL grammar productions that are typically ignored (whitespace,
  * comments, etc.).
  */
private[parser] sealed trait Ignored extends PositionTracking { this: Parser =>

  /** Whether comments should be parsed into the resulting AST. */
  protected[this] def parseComments: Boolean

  private[this] val WhiteSpace = CharPredicate("\u0009\u0020")

  protected[this] def CRLF: Rule0 = rule('\u000D' ~ '\u000A')

  protected[this] val LineTerminator: CharPredicate = CharPredicate("\u000A")

  private[this] val UnicodeBOM = CharPredicate('\uFEFF')

  protected[this] def Ignored: Rule0 = rule {
    quiet(UnicodeBOM | WhiteSpace | (CRLF | LineTerminator) ~ trackNewLine | Comment | ',')
  }

  protected[this] def IgnoredNoComment: Rule0 = rule {
    quiet(UnicodeBOM | WhiteSpace | (CRLF | LineTerminator) ~ trackNewLine | ',')
  }

  protected[this] def Comments: Rule1[Vector[ast.Comment]] = rule {
    test(
      parseComments) ~ CommentCap.* ~ Ignored.* ~> (_.toVector) | CommentNoCap.* ~ Ignored.* ~ push(
      Vector.empty)
  }

  private[this] def CommentCap = rule {
    trackPos ~ "#" ~ capture(CommentChar.*) ~ IgnoredNoComment.* ~> ((location, comment) =>
      ast.Comment(comment, location))
  }

  private[this] def CommentNoCap: Rule0 = rule("#" ~ CommentChar.* ~ IgnoredNoComment.*)

  private[this] def Comment = rule("#" ~ CommentChar.*)

  private[this] def CommentChar = rule(!(CRLF | LineTerminator) ~ ANY)

  protected[this] def ws(char: Char): Rule0 = rule(quiet(Ignored.* ~ ch(char) ~ Ignored.*))

  protected[this] def wsNoComment(char: Char): Rule0 = rule(
    quiet(Ignored.* ~ ch(char) ~ IgnoredNoComment.*))

  protected[this] def wsCapture(s: String): Rule1[String] = rule(
    quiet(Ignored.* ~ capture(str(s)) ~ IgnoredNoComment.*))
}

private[parser] sealed trait Document {
  this: Parser
    with Tokens
    with Operations
    with Ignored
    with Fragments
    with Operations
    with Values
    with Types
    with Directives
    with TypeSystemDefinitions =>

  protected[parser] def Document =
    rule {
      IgnoredNoComment.* ~ trackPos ~ Definition.+ ~ IgnoredNoComment.* ~ Comments ~ EOI ~>
        ((location, d, comments) => ast.Document(d.toVector, comments, location))
    }

  protected[parser] def InputDocument = rule {
    IgnoredNoComment.* ~ trackPos ~ ValueConst.+ ~ IgnoredNoComment.* ~ Comments ~ EOI ~>
      ((location, vs, comments) => ast.InputDocument(vs.toVector, comments, location))
  }

  protected[parser] def InputDocumentWithVariables = rule {
    IgnoredNoComment.* ~ trackPos ~ Value.+ ~ IgnoredNoComment.* ~ Comments ~ EOI ~>
      ((location, vs, comments) => ast.InputDocument(vs.toVector, comments, location))
  }

  private[this] def Definition = rule {
    ExecutableDefinition |
      TypeSystemDefinition |
      TypeSystemExtension
  }

  private[this] def ExecutableDefinition = rule {
    OperationDefinition |
      FragmentDefinition
  }
}

private[parser] sealed trait TypeSystemDefinitions {
  this: Parser
    with Tokens
    with Ignored
    with Directives
    with Types
    with Operations
    with Values
    with Fragments =>

  private[this] def scalar = rule(Keyword("scalar"))
  private[this] def `type` = rule(Keyword("type"))
  private[this] def interface = rule(Keyword("interface"))
  private[this] def union = rule(Keyword("union"))
  private[this] def `enum` = rule(Keyword("enum"))
  private[this] def inputType = rule(Keyword("input"))
  private[this] def implements = rule(Keyword("implements"))
  private[this] def extend = rule(Keyword("extend"))
  private[this] def directive = rule(Keyword("directive"))
  private[this] def schema = rule(Keyword("schema"))

  protected[this] def TypeSystemDefinition = rule {
    SchemaDefinition |
      TypeDefinition |
      DirectiveDefinition
  }

  private[this] def TypeDefinition = rule {
    ScalarTypeDefinition |
      ObjectTypeDefinition |
      InterfaceTypeDefinition |
      UnionTypeDefinition |
      EnumTypeDefinition |
      InputObjectTypeDefinition
  }

  private[this] def ScalarTypeDefinition = rule {
    Description ~ Comments ~ trackPos ~ scalar ~ Name ~ (DirectivesConst.? ~> (_.getOrElse(
      Vector.empty))) ~> ((descr, comment, location, name, dirs) =>
      ast.ScalarTypeDefinition(name, dirs, descr, comment, location))
  }

  private[this] def ObjectTypeDefinition = rule {
    Description ~ Comments ~ trackPos ~ `type` ~ Name ~ (ImplementsInterfaces.? ~> (_.getOrElse(
      Vector.empty))) ~ (DirectivesConst.? ~> (_.getOrElse(Vector.empty))) ~ FieldsDefinition.? ~> (
      (descr, comment, location, name, interfaces, dirs, fields) =>
        ast.ObjectTypeDefinition(
          name,
          interfaces,
          fields.fold(Vector.empty[ast.FieldDefinition])(_._1.toVector),
          dirs,
          descr,
          comment,
          fields.fold(Vector.empty[ast.Comment])(_._2),
          location
        ))
  }

  protected[this] def TypeSystemExtension = rule {
    SchemaExtension |
      TypeExtension
  }

  private[this] def TypeExtension = rule {
    ScalarTypeExtensionDefinition |
      ObjectTypeExtensionDefinition |
      InterfaceTypeExtensionDefinition |
      UnionTypeExtensionDefinition |
      EnumTypeExtensionDefinition |
      InputObjectTypeExtensionDefinition
  }

  private[this] def SchemaExtension = rule {
    (Comments ~ trackPos ~ extend ~ schema ~ (DirectivesConst.? ~> (_.getOrElse(
      Vector.empty))) ~ wsNoComment('{') ~ OperationTypeDefinition.+ ~ Comments ~ wsNoComment(
      '}') ~> ((comment, location, dirs, ops, tc) =>
      ast.SchemaExtensionDefinition(ops.toVector, dirs, comment, tc, location))) |
      (Comments ~ trackPos ~ extend ~ schema ~ DirectivesConst ~> ((comment, location, dirs) =>
        ast.SchemaExtensionDefinition(Vector.empty, dirs, comment, Vector.empty, location)))
  }

  private[this] def ObjectTypeExtensionDefinition = rule {
    (Comments ~ trackPos ~ extend ~ `type` ~ Name ~ (ImplementsInterfaces.? ~> (_.getOrElse(
      Vector.empty))) ~ (DirectivesConst.? ~> (_.getOrElse(Vector.empty))) ~ FieldsDefinition ~> (
      (comment, location, name, interfaces, dirs, fields) =>
        ast.ObjectTypeExtensionDefinition(
          name,
          interfaces,
          fields._1.toVector,
          dirs,
          comment,
          fields._2,
          location))) |
      (Comments ~ trackPos ~ extend ~ `type` ~ Name ~ (ImplementsInterfaces.? ~> (_.getOrElse(
        Vector.empty))) ~ DirectivesConst ~> ((comment, location, name, interfaces, dirs) =>
        ast.ObjectTypeExtensionDefinition(
          name,
          interfaces,
          Vector.empty,
          dirs,
          comment,
          Vector.empty,
          location))) |
      (Comments ~ trackPos ~ extend ~ `type` ~ Name ~ ImplementsInterfaces ~> (
        (comment, location, name, interfaces) =>
          ast.ObjectTypeExtensionDefinition(
            name,
            interfaces,
            Vector.empty,
            Vector.empty,
            comment,
            Vector.empty,
            location)))
  }

  private[this] def InterfaceTypeExtensionDefinition = rule {
    (Comments ~ trackPos ~ extend ~ interface ~ Name ~ (ImplementsInterfaces.? ~> (_.getOrElse(
      Vector.empty))) ~ (DirectivesConst.? ~> (_.getOrElse(Vector.empty))) ~ FieldsDefinition ~> (
      (comment, location, name, interfaces, dirs, fields) =>
        ast.InterfaceTypeExtensionDefinition(
          name,
          interfaces,
          fields._1.toVector,
          dirs,
          comment,
          fields._2,
          location))) |
      (Comments ~ trackPos ~ extend ~ interface ~ Name ~ (ImplementsInterfaces.? ~> (_.getOrElse(
        Vector.empty))) ~ DirectivesConst ~> ((comment, location, name, interfaces, dirs) =>
        ast.InterfaceTypeExtensionDefinition(
          name,
          interfaces,
          Vector.empty,
          dirs,
          comment,
          Vector.empty,
          location))) |
      Comments ~ trackPos ~ extend ~ interface ~ Name ~ ImplementsInterfaces ~> (
        (comment, location, name, interfaces) =>
          ast.InterfaceTypeExtensionDefinition(
            name,
            interfaces,
            Vector.empty,
            Vector.empty,
            comment,
            Vector.empty,
            location
          ))
  }

  private[this] def UnionTypeExtensionDefinition = rule {
    (Comments ~ trackPos ~ extend ~ union ~ Name ~ (DirectivesConst.? ~> (_.getOrElse(
      Vector.empty))) ~ UnionMemberTypes ~> ((comment, location, name, dirs, types) =>
      ast.UnionTypeExtensionDefinition(name, types, dirs, comment, location))) |
      (Comments ~ trackPos ~ extend ~ union ~ Name ~ DirectivesConst ~> (
        (comment, location, name, dirs) =>
          ast.UnionTypeExtensionDefinition(name, Vector.empty, dirs, comment, location)))
  }

  private[this] def EnumTypeExtensionDefinition = rule {
    (Comments ~ trackPos ~ extend ~ `enum` ~ Name ~ (DirectivesConst.? ~> (_.getOrElse(
      Vector.empty))) ~ EnumValuesDefinition ~> ((comment, location, name, dirs, values) =>
      ast.EnumTypeExtensionDefinition(
        name,
        values._1.toVector,
        dirs,
        comment,
        values._2,
        location))) |
      (Comments ~ trackPos ~ extend ~ `enum` ~ Name ~ DirectivesConst ~> (
        (comment, location, name, dirs) =>
          ast.EnumTypeExtensionDefinition(
            name,
            Vector.empty,
            dirs,
            comment,
            Vector.empty,
            location)))
  }

  private[this] def InputObjectTypeExtensionDefinition = rule {
    (Comments ~ trackPos ~ extend ~ inputType ~ Name ~ (DirectivesConst.? ~> (_.getOrElse(
      Vector.empty))) ~ InputFieldsDefinition ~> ((comment, location, name, dirs, fields) =>
      ast.InputObjectTypeExtensionDefinition(
        name,
        fields._1.toVector,
        dirs,
        comment,
        fields._2,
        location))) |
      (Comments ~ trackPos ~ extend ~ inputType ~ Name ~ DirectivesConst ~> (
        (comment, location, name, dirs) =>
          ast.InputObjectTypeExtensionDefinition(
            name,
            Vector.empty,
            dirs,
            comment,
            Vector.empty,
            location)))
  }

  private[this] def ScalarTypeExtensionDefinition = rule {
    Comments ~ trackPos ~ extend ~ scalar ~ Name ~ DirectivesConst ~> (
      (comment, location, name, dirs) =>
        ast.ScalarTypeExtensionDefinition(name, dirs, comment, location))
  }

  private[this] def ImplementsInterfaces = rule {
    implements ~ ws('&').? ~ NamedType.+(ws('&')) ~> (_.toVector)
  }

  private[this] def FieldsDefinition = rule {
    wsNoComment('{') ~ FieldDefinition.+ ~ Comments ~ wsNoComment('}') ~> (_ -> _)
  }

  private[this] def FieldDefinition = rule {
    Description ~ Comments ~ trackPos ~ Name ~ (ArgumentsDefinition.? ~> (_.getOrElse(
      Vector.empty))) ~ ws(':') ~ Type ~ (Directives.? ~> (_.getOrElse(Vector.empty))) ~> (
      (descr, comment, location, name, args, fieldType, dirs) =>
        ast.FieldDefinition(name, fieldType, args, dirs, descr, comment, location))
  }

  private[this] def ArgumentsDefinition = rule {
    wsNoComment('(') ~ InputValueDefinition.+ ~ wsNoComment(')') ~> (_.toVector)
  }

  private[this] def InputValueDefinition = rule {
    Description ~ Comments ~ trackPos ~ Name ~ ws(
      ':') ~ Type ~ DefaultValue.? ~ (DirectivesConst.? ~> (_.getOrElse(Vector.empty))) ~> (
      (descr, comment, location, name, valueType, default, dirs) =>
        ast.InputValueDefinition(name, valueType, default, dirs, descr, comment, location))
  }

  private[this] def InterfaceTypeDefinition = rule {
    Description ~ Comments ~ trackPos ~ interface ~ Name ~ (ImplementsInterfaces.? ~> (_.getOrElse(
      Vector.empty))) ~ (DirectivesConst.? ~> (_.getOrElse(Vector.empty))) ~ FieldsDefinition.? ~> (
      (descr, comment, location, name, interfaces, dirs, fields) =>
        ast.InterfaceTypeDefinition(
          name,
          fields.fold(Vector.empty[ast.FieldDefinition])(_._1.toVector),
          interfaces,
          dirs,
          descr,
          comment,
          fields.fold(Vector.empty[ast.Comment])(_._2),
          location
        ))
  }

  private[this] def UnionTypeDefinition = rule {
    Description ~ Comments ~ trackPos ~ union ~ Name ~ (DirectivesConst.? ~> (_.getOrElse(
      Vector.empty))) ~ (UnionMemberTypes.? ~> (_.getOrElse(Vector.empty))) ~> (
      (descr, comment, location, name, dirs, members) =>
        ast.UnionTypeDefinition(name, members, dirs, descr, comment, location))
  }

  private[this] def UnionMemberTypes = rule(wsNoComment('=') ~ UnionMembers)

  private[this] def UnionMembers = rule(ws('|').? ~ NamedType.+(ws('|')) ~> (_.toVector))

  private[this] def EnumTypeDefinition = rule {
    Description ~ Comments ~ trackPos ~ `enum` ~ Name ~ (DirectivesConst.? ~> (_.getOrElse(
      Vector.empty))) ~ EnumValuesDefinition.? ~> ((descr, comment, location, name, dirs, values) =>
      ast.EnumTypeDefinition(
        name,
        values.fold(Vector.empty[ast.EnumValueDefinition])(_._1.toVector),
        dirs,
        descr,
        comment,
        values.fold(Vector.empty[ast.Comment])(_._2),
        location))
  }

  private[this] def EnumValuesDefinition = rule {
    wsNoComment('{') ~ EnumValueDefinition.+ ~ Comments ~ wsNoComment('}') ~> (_ -> _)
  }

  private[this] def EnumValueDefinition = rule {
    Description ~ Comments ~ trackPos ~ Name ~ (DirectivesConst.? ~> (_.getOrElse(
      Vector.empty))) ~> ((descr, comments, location, name, dirs) =>
      ast.EnumValueDefinition(name, dirs, descr, comments, location))
  }

  private[this] def InputObjectTypeDefinition = rule {
    Description ~ Comments ~ trackPos ~ inputType ~ Name ~ (DirectivesConst.? ~> (_.getOrElse(
      Vector.empty))) ~ InputFieldsDefinition.? ~> (
      (descr, comment, location, name, dirs, fields) =>
        ast.InputObjectTypeDefinition(
          name,
          fields.fold(Vector.empty[ast.InputValueDefinition])(_._1.toVector),
          dirs,
          descr,
          comment,
          fields.fold(Vector.empty[ast.Comment])(_._2),
          location))
  }

  private[this] def InputFieldsDefinition = rule {
    wsNoComment('{') ~ InputValueDefinition.+ ~ Comments ~ wsNoComment('}') ~> (_ -> _)
  }

  private def repeatable = rule(capture(Keyword("repeatable")).? ~> (_.isDefined))

  private[this] def DirectiveDefinition = rule {
    Description ~ Comments ~ trackPos ~ directive ~ '@' ~ NameStrict ~ (ArgumentsDefinition.? ~> (_.getOrElse(
      Vector.empty))) ~ repeatable ~ on ~ DirectiveLocations ~> (
      (descr, comment, location, name, args, rep, locations) =>
        ast.DirectiveDefinition(name, args, locations, descr, rep, comment, location))
  }

  private[this] def DirectiveLocations = rule {
    ws('|').? ~ DirectiveLocation.+(wsNoComment('|')) ~> (_.toVector)
  }

  private[this] def DirectiveLocation = rule {
    Comments ~ trackPos ~ DirectiveLocationName ~> ((comment, location, name) =>
      ast.DirectiveLocation(name, comment, location))
  }

  private[this] def DirectiveLocationName = rule {
    TypeSystemDirectiveLocation | ExecutableDirectiveLocation
  }

  private[this] def ExecutableDirectiveLocation = rule {
    wsCapture("QUERY") |
      wsCapture("MUTATION") |
      wsCapture("SUBSCRIPTION") |
      wsCapture("FIELD") |
      wsCapture("FRAGMENT_DEFINITION") |
      wsCapture("FRAGMENT_SPREAD") |
      wsCapture("INLINE_FRAGMENT")
  }

  private[this] def TypeSystemDirectiveLocation = rule {
    wsCapture("SCHEMA") |
      wsCapture("SCALAR") |
      wsCapture("OBJECT") |
      wsCapture("FIELD_DEFINITION") |
      wsCapture("ARGUMENT_DEFINITION") |
      wsCapture("INTERFACE") |
      wsCapture("UNION") |
      wsCapture("ENUM_VALUE") |
      wsCapture("ENUM") |
      wsCapture("INPUT_OBJECT") |
      wsCapture("INPUT_FIELD_DEFINITION") |
      wsCapture("VARIABLE_DEFINITION")
  }

  private[this] def SchemaDefinition = rule {
    Description ~ Comments ~ trackPos ~ schema ~ (DirectivesConst.? ~> (_.getOrElse(
      Vector.empty))) ~ wsNoComment('{') ~ OperationTypeDefinition.+ ~ Comments ~ wsNoComment(
      '}') ~> ((descr, comment, location, dirs, ops, tc) =>
      ast.SchemaDefinition(ops.toVector, dirs, descr, comment, tc, location))
  }

  private[this] def OperationTypeDefinition = rule {
    Comments ~ trackPos ~ OperationType ~ ws(':') ~ NamedType ~> (
      (comment, location, opType, tpe) =>
        ast.OperationTypeDefinition(opType, tpe, comment, location))
  }

  private[this] def Description = rule(StringValue.?)
}

private[parser] sealed trait Operations extends PositionTracking {
  this: Parser with Tokens with Ignored with Fragments with Values with Types with Directives =>

  protected[this] def OperationDefinition = rule {
    Comments ~ trackPos ~ SelectionSet ~> ((comment, location, s) =>
      ast.OperationDefinition(
        selections = s._1,
        comments = comment,
        trailingComments = s._2,
        location = location)) |
      Comments ~ trackPos ~ OperationType ~ OperationName.? ~ (VariableDefinitions.? ~> (_.getOrElse(
        Vector.empty))) ~ (Directives.? ~> (_.getOrElse(Vector.empty))) ~ SelectionSet ~>
      ((comment, location, opType, name, vars, dirs, sels) =>
        ast.OperationDefinition(opType, name, vars, dirs, sels._1, comment, sels._2, location))
  }

  private[this] def OperationName = rule(Name)

  protected[this] def OperationType = rule {
    Query ~ push(ast.OperationType.Query) |
      Mutation ~ push(ast.OperationType.Mutation) |
      Subscription ~ push(ast.OperationType.Subscription)
  }

  private[this] def Query = rule(Keyword("query"))
  private[this] def Mutation = rule(Keyword("mutation"))
  private[this] def Subscription = rule(Keyword("subscription"))

  protected[this] def VariableDefinitions = rule {
    wsNoComment('(') ~ VariableDefinition.+ ~ wsNoComment(')') ~> (_.toVector)
  }

  private[this] def VariableDefinition = rule {
    Comments ~ trackPos ~ Variable ~ ws(
      ':') ~ Type ~ DefaultValue.? ~ (DirectivesConst.? ~> (_.getOrElse(Vector.empty))) ~>
      ((comment, location, name, tpe, defaultValue, dirs) =>
        ast.VariableDefinition(name, tpe, defaultValue, dirs, comment, location))
  }

  protected[this] def Variable: Rule1[String] = rule(Ignored.* ~ '$' ~ NameStrict)

  protected[this] def DefaultValue: Rule1[ast.Value] =
    rule(wsNoComment('=') ~ ValueConst)

  protected[this] def SelectionSet: Rule1[(Vector[ast.Selection], Vector[ast.Comment])] = rule {
    wsNoComment('{') ~ Selection.+ ~ Comments ~ wsNoComment('}') ~>
      ((x: Seq[ast.Selection], comments: Vector[ast.Comment]) => x.toVector -> comments)
  }

  private[this] def Selection = rule(Field | FragmentSpread | InlineFragment)

  private[this] def Field = rule {
    Comments ~ trackPos ~ Alias.? ~ Name ~
      (Arguments.? ~> (_.getOrElse(Vector.empty))) ~
      (Directives.? ~> (_.getOrElse(Vector.empty))) ~
      (SelectionSet.? ~> (_.getOrElse(Vector.empty -> Vector.empty))) ~>
      ((comment, location, alias, name, args, dirs, sels) =>
        ast.Field(alias, name, args, dirs, sels._1, comment, sels._2, location))
  }

  private[this] def Alias = rule(Name ~ ws(':'))

  protected[this] def Arguments = rule {
    Ignored.* ~ wsNoComment('(') ~ Argument.+ ~ wsNoComment(')') ~> (_.toVector)
  }

  protected[this] def ArgumentsConst = rule {
    Ignored.* ~ wsNoComment('(') ~ ArgumentConst.+ ~ wsNoComment(')') ~> (_.toVector)
  }

  private[this] def Argument = rule {
    Comments ~ trackPos ~ Name ~ wsNoComment(':') ~ Value ~> ((comment, location, name, value) =>
      ast.Argument(name, value, comment, location))
  }

  private[this] def ArgumentConst = rule {
    Comments ~ trackPos ~ Name ~ wsNoComment(':') ~ ValueConst ~> (
      (comment, location, name, value) => ast.Argument(name, value, comment, location))
  }
}

private[parser] sealed trait Fragments {
  this: Parser with Tokens with Ignored with Directives with Types with Operations with Values =>

  protected[this] def experimentalFragmentVariables: Boolean

  protected[this] def FragmentSpread: Rule1[ast.FragmentSpread] = rule {
    Comments ~ trackPos ~ Ellipsis ~ FragmentName ~ (Directives.? ~> (_.getOrElse(Vector.empty))) ~>
      ((comment, location, name, dirs) => ast.FragmentSpread(name, dirs, comment, location))
  }

  protected[this] def InlineFragment = rule {
    Comments ~ trackPos ~ Ellipsis ~ TypeCondition.? ~ (Directives.? ~> (_.getOrElse(
      Vector.empty))) ~ SelectionSet ~>
      ((comment, location, typeCondition, dirs, sels) =>
        ast.InlineFragment(typeCondition, dirs, sels._1, comment, sels._2, location))
  }

  protected[this] def on: Rule0 = rule(Keyword("on"))
  private[this] def Fragment = rule(Keyword("fragment"))

  protected[this] def FragmentDefinition = rule {
    Comments ~ trackPos ~ Fragment ~ FragmentName ~ ExperimentalFragmentVariables ~ TypeCondition ~ (Directives.? ~> (_.getOrElse(
      Vector.empty))) ~ SelectionSet ~>
      ((comment, location, name, vars, typeCondition, dirs, sels) =>
        ast.FragmentDefinition(
          name,
          typeCondition,
          dirs,
          sels._1,
          vars,
          comment,
          sels._2,
          location))
  }

  private[this] def ExperimentalFragmentVariables = rule {
    test(experimentalFragmentVariables) ~ VariableDefinitions.? ~> (_.getOrElse(
      Vector.empty)) | push(Vector.empty)
  }

  private[this] def FragmentName = rule(!on ~ Name)
  private[this] def TypeCondition = rule(on ~ NamedType)
}

private[parser] sealed trait Values {
  this: Parser
    with Tokens
    with Ignored
    with Operations
    with Fragments
    with Values
    with Types
    with Directives =>
  protected[this] def ValueConst: Rule1[ast.Value] = rule {
    NumberValue | StringValue | BooleanValue | NullValue | EnumValue | ListValueConst | ObjectValueConst
  }

  protected[this] def Value: Rule1[ast.Value] = rule {
    Comments ~ trackPos ~ Variable ~> ((comment, location, name) =>
      ast.VariableValue(name, comment, location)) |
      NumberValue |
      StringValue |
      BooleanValue |
      NullValue |
      EnumValue |
      ListValue |
      ObjectValue
  }

  private[this] def BooleanValue = rule {
    Comments ~ trackPos ~ True ~> ((comment, location) =>
      ast.BooleanValue(true, comment, location)) |
      Comments ~ trackPos ~ False ~> ((comment, location) =>
        ast.BooleanValue(false, comment, location))
  }

  private[this] def True = rule(Keyword("true"))

  private[this] def False = rule(Keyword("false"))

  private[this] def Null = rule(Keyword("null"))

  private[this] def NullValue = rule {
    Comments ~ trackPos ~ Null ~> ((comment, location) => ast.NullValue(comment, location))
  }

  private[this] def EnumValue = rule {
    Comments ~ !(True | False) ~ trackPos ~ Name ~> ((comment, location, name) =>
      ast.EnumValue(name, comment, location))
  }

  private[this] def ListValueConst = rule {
    Comments ~ trackPos ~ wsNoComment('[') ~ ValueConst.* ~ wsNoComment(']') ~> (
      (comment, location, v) => ast.ListValue(v.toVector, comment, location))
  }

  private[this] def ListValue = rule {
    Comments ~ trackPos ~ wsNoComment('[') ~ Value.* ~ wsNoComment(']') ~> (
      (comment, location, v) => ast.ListValue(v.toVector, comment, location))
  }

  private[this] def ObjectValueConst = rule {
    Comments ~ trackPos ~ wsNoComment('{') ~ ObjectFieldConst.* ~ wsNoComment('}') ~> (
      (comment, location, f) => ast.ObjectValue(f.toVector, comment, location))
  }

  private[this] def ObjectValue = rule {
    Comments ~ trackPos ~ wsNoComment('{') ~ ObjectField.* ~ wsNoComment('}') ~> (
      (comment, location, f) => ast.ObjectValue(f.toVector, comment, location))
  }

  private[this] def ObjectFieldConst = rule {
    Comments ~ trackPos ~ Name ~ wsNoComment(':') ~ ValueConst ~> (
      (comment, location, name, value) => ast.ObjectField(name, value, comment, location))
  }

  private[this] def ObjectField = rule {
    Comments ~ trackPos ~ Name ~ wsNoComment(':') ~ Value ~> ((comment, location, name, value) =>
      ast.ObjectField(name, value, comment, location))
  }
}

private[parser] sealed trait Directives {
  this: Parser with Tokens with Operations with Ignored with Fragments with Values with Types =>
  protected[this] def Directives = rule(Directive.+ ~> (_.toVector))
  protected[this] def DirectivesConst = rule(DirectiveConst.+ ~> (_.toVector))

  private[this] def Directive = rule {
    Comments ~ trackPos ~ '@' ~ NameStrict ~ (Arguments.? ~> (_.getOrElse(Vector.empty))) ~>
      ((comment, location, name, args) => ast.Directive(name, args, comment, location))
  }

  private[this] def DirectiveConst = rule {
    Comments ~ trackPos ~ '@' ~ NameStrict ~ (ArgumentsConst.? ~> (_.getOrElse(Vector.empty))) ~>
      ((comment, location, name, args) => ast.Directive(name, args, comment, location))
  }
}

private[parser] sealed trait Types { this: Parser with Tokens with Ignored =>
  protected[this] def Type: Rule1[ast.Type] = rule(NonNullType | ListType | NamedType)

  private[this] def TypeName = rule(Name)

  protected[this] def NamedType: Rule1[ast.NamedType] = rule {
    Ignored.* ~ trackPos ~ TypeName ~> ((location, name) => ast.NamedType(name, location))
  }

  private[this] def ListType = rule {
    trackPos ~ ws('[') ~ Type ~ wsNoComment(']') ~> ((location, tpe) => ast.ListType(tpe, location))
  }

  private[this] def NonNullType = rule {
    trackPos ~ TypeName ~ wsNoComment('!') ~> ((location, name) =>
      ast.NotNullType(ast.NamedType(name, location), location)) |
      trackPos ~ ListType ~ wsNoComment('!') ~> ((location, tpe) => ast.NotNullType(tpe, location))
  }
}

class QueryParser private (
    val input: ParserInput,
    val sourceId: String,
    val experimentalFragmentVariables: Boolean = false,
    val parseLocations: Boolean = true,
    override val parseComments: Boolean = true
) extends Parser
    with Tokens
    with Ignored
    with Document
    with Operations
    with Fragments
    with Values
    with Directives
    with Types
    with TypeSystemDefinitions

/** GraphQL parsing methods.
  *
  * The parser is written with [[https://github.com/sirthias/parboiled2 Parboiled2]]. Each of the
  * method types takes either a string, or a Parboiled2 `ParserInput` for more generality.
  */
object QueryParser {

  /** Parse the given string as a GraphQL ''Document''. */
  def parse(input: String, config: ParserConfig = ParserConfig.default): Try[ast.Document] =
    parse(ParserInput(input), config)

  /** Parse the given input as a GraphQL ''Document''. */
  def parse(input: ParserInput, config: ParserConfig): Try[ast.Document] = {
    val id = config.sourceIdFn(input)
    val parser = new QueryParser(
      input,
      id,
      config.experimentalFragmentVariables,
      config.parseLocations,
      config.parseComments)

    parser.Document.run() match {
      case Success(res) =>
        Success(res.copy(sourceMapper = config.sourceMapperFn(id, input)))
      case Failure(e: ParseError) => Failure(SyntaxError(parser, input, e))
      case f @ Failure(_) => f
    }
  }

  /** Parse the given string as a GraphQL ''InputDocument'' and return its first ''Value''. */
  def parseInput(input: String): Try[ast.Value] = parseInput(ParserInput(input))

  /** Parse the given input as a GraphQL ''InputDocument'' and return its first ''Value''. */
  def parseInput(input: ParserInput): Try[ast.Value] = {
    val parser = new QueryParser(input, "")

    parser.InputDocument.run() match {
      case Success(res) if res.values.nonEmpty => Success(res.values.head)
      case Success(_) =>
        Failure(
          new IllegalArgumentException("Input document does not contain any value definitions.")
        )
      case Failure(e: ParseError) => Failure(SyntaxError(parser, input, e))
      case Failure(e) => Failure(e)
    }
  }

  /** Parse the given string as a GraphQL ''InputDocument''. */
  def parseInputDocument(
      input: String,
      config: ParserConfig = ParserConfig.default): Try[ast.InputDocument] =
    parseInputDocument(ParserInput(input), config)

  /** Parse the given input as a GraphQL ''InputDocument''. */
  def parseInputDocument(input: ParserInput, config: ParserConfig): Try[ast.InputDocument] = {
    val id = config.sourceIdFn(input)
    val parser = new QueryParser(
      input,
      id,
      config.experimentalFragmentVariables,
      config.parseLocations,
      config.parseComments)

    parser.InputDocument.run() match {
      case Success(res) => Success(res.copy(sourceMapper = config.sourceMapperFn(id, input)))
      case Failure(e: ParseError) => Failure(SyntaxError(parser, input, e))
      case f @ Failure(_) => f
    }
  }

  /** Parse the given string as a GraphQL ''InputDocumentWithVariables'' and return its first
    * ''Value''.
    */
  def parseInputWithVariables(input: String): Try[ast.Value] =
    parseInputWithVariables(ParserInput(input))

  /** Parse the given input as a GraphQL ''InputDocumentWithVariables'' and return its first
    * ''Value''.
    */
  def parseInputWithVariables(input: ParserInput): Try[ast.Value] = {
    val parser = new QueryParser(input, "")

    parser.InputDocumentWithVariables.run() match {
      case Success(res) if res.values.nonEmpty => Success(res.values.head)
      case Success(_) =>
        Failure(
          new IllegalArgumentException("Input document does not contain any value definitions.")
        )
      case Failure(e: ParseError) => Failure(SyntaxError(parser, input, e))
      case Failure(e) => Failure(e)
    }
  }

  /** Parse the given string as a GraphQL ''InputDocumentWithVariables''. */
  def parseInputDocumentWithVariables(
      input: String,
      config: ParserConfig = ParserConfig.default): Try[ast.InputDocument] =
    parseInputDocumentWithVariables(ParserInput(input), config)

  /** Parse the given input as a GraphQL ''InputDocumentWithVariables''. */
  def parseInputDocumentWithVariables(
      input: ParserInput,
      config: ParserConfig): Try[ast.InputDocument] = {
    val id = config.sourceIdFn(input)
    val parser = new QueryParser(input, id)

    parser.InputDocumentWithVariables.run() match {
      case Success(res) => Success(res.copy(sourceMapper = config.sourceMapperFn(id, input)))
      case Failure(e: ParseError) => Failure(SyntaxError(parser, input, e))
      case f @ Failure(_) => f
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy