mirror of
				https://github.com/TheAnachronism/docspell.git
				synced 2025-11-03 18:00:11 +00:00 
			
		
		
		
	Provide custom error structure for parse failures
This commit is contained in:
		@@ -9,17 +9,18 @@ import docspell.query.internal.ExprUtil
 | 
				
			|||||||
object ItemQueryParser {
 | 
					object ItemQueryParser {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @JSExport
 | 
					  @JSExport
 | 
				
			||||||
  def parse(input: String): Either[String, ItemQuery] =
 | 
					  def parse(input: String): Either[ParseFailure, ItemQuery] =
 | 
				
			||||||
    if (input.isEmpty) Right(ItemQuery.all)
 | 
					    if (input.isEmpty) Right(ItemQuery.all)
 | 
				
			||||||
    else {
 | 
					    else {
 | 
				
			||||||
      val in = if (input.charAt(0) == '(') input else s"(& $input )"
 | 
					      val in = if (input.charAt(0) == '(') input else s"(& $input )"
 | 
				
			||||||
      ExprParser
 | 
					      ExprParser
 | 
				
			||||||
        .parseQuery(in)
 | 
					        .parseQuery(in)
 | 
				
			||||||
        .left
 | 
					        .left
 | 
				
			||||||
        .map(pe => s"Error parsing: '$input': $pe") //TODO
 | 
					        .map(ParseFailure.fromError(in))
 | 
				
			||||||
        .map(q => q.copy(expr = ExprUtil.reduce(q.expr)))
 | 
					        .map(q => q.copy(expr = ExprUtil.reduce(q.expr)))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def parseUnsafe(input: String): ItemQuery =
 | 
					  def parseUnsafe(input: String): ItemQuery =
 | 
				
			||||||
    parse(input).fold(sys.error, identity)
 | 
					    parse(input).fold(m => sys.error(m.render), identity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					package docspell.query
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cats.data.{NonEmptyList => Nel}
 | 
				
			||||||
 | 
					import cats.parse.Parser
 | 
				
			||||||
 | 
					import cats.parse.Parser.Expectation.EndOfString
 | 
				
			||||||
 | 
					import cats.parse.Parser.Expectation.ExpectedFailureAt
 | 
				
			||||||
 | 
					import cats.parse.Parser.Expectation.Fail
 | 
				
			||||||
 | 
					import cats.parse.Parser.Expectation.FailWith
 | 
				
			||||||
 | 
					import cats.parse.Parser.Expectation.InRange
 | 
				
			||||||
 | 
					import cats.parse.Parser.Expectation.Length
 | 
				
			||||||
 | 
					import cats.parse.Parser.Expectation.OneOfStr
 | 
				
			||||||
 | 
					import cats.parse.Parser.Expectation.StartOfString
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final case class ParseFailure(
 | 
				
			||||||
 | 
					    input: String,
 | 
				
			||||||
 | 
					    failedAt: Int,
 | 
				
			||||||
 | 
					    messages: Nel[ParseFailure.Message]
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def render: String = {
 | 
				
			||||||
 | 
					    val items = messages.map(_.msg).toList.mkString(", ")
 | 
				
			||||||
 | 
					    s"Failed to read input near $failedAt: $input\nDetails: $items"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object ParseFailure {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final case class Message(offset: Int, msg: String)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private[query] def fromError(input: String)(pe: Parser.Error): ParseFailure =
 | 
				
			||||||
 | 
					    ParseFailure(
 | 
				
			||||||
 | 
					      input,
 | 
				
			||||||
 | 
					      pe.failedAtOffset,
 | 
				
			||||||
 | 
					      Parser.Expectation.unify(pe.expected).map(expectationToMsg)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private[query] def expectationToMsg(e: Parser.Expectation): Message =
 | 
				
			||||||
 | 
					    e match {
 | 
				
			||||||
 | 
					      case StartOfString(offset) =>
 | 
				
			||||||
 | 
					        Message(offset, "Expected start of string")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case FailWith(offset, message) =>
 | 
				
			||||||
 | 
					        Message(offset, message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case InRange(offset, lower, upper) =>
 | 
				
			||||||
 | 
					        if (lower == upper) Message(offset, s"Expected character: $lower")
 | 
				
			||||||
 | 
					        else Message(offset, s"Expected character from range: [$lower .. $upper]")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case Length(offset, expected, actual) =>
 | 
				
			||||||
 | 
					        Message(offset, s"Expected input of length $expected, but got $actual")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case ExpectedFailureAt(offset, matched) =>
 | 
				
			||||||
 | 
					        Message(offset, s"Expected failing, but matched '$matched'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case EndOfString(offset, length) =>
 | 
				
			||||||
 | 
					        Message(offset, s"Expected end of string at length: $length")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case Fail(offset) =>
 | 
				
			||||||
 | 
					        Message(offset, s"Failed to parse near $offset")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case OneOfStr(offset, strs) =>
 | 
				
			||||||
 | 
					        val options = strs.mkString(", ")
 | 
				
			||||||
 | 
					        Message(offset, s"Expected one of the following strings: $options")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -61,7 +61,7 @@ object ItemRoutes {
 | 
				
			|||||||
        val of = offset.getOrElse(0)
 | 
					        val of = offset.getOrElse(0)
 | 
				
			||||||
        query match {
 | 
					        query match {
 | 
				
			||||||
          case Left(err) =>
 | 
					          case Left(err) =>
 | 
				
			||||||
            BadRequest(BasicResult(false, err))
 | 
					            BadRequest(BasicResult(false, err.render))
 | 
				
			||||||
          case Right(sq) =>
 | 
					          case Right(sq) =>
 | 
				
			||||||
            for {
 | 
					            for {
 | 
				
			||||||
              items <- backend.itemSearch.findItems(cfg.maxNoteLength)(
 | 
					              items <- backend.itemSearch.findItems(cfg.maxNoteLength)(
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user