mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-04 10:29:34 +00:00
Provide custom error structure for parse failures
This commit is contained in:
parent
d737da768e
commit
e079ec1987
@ -9,17 +9,18 @@ import docspell.query.internal.ExprUtil
|
||||
object ItemQueryParser {
|
||||
|
||||
@JSExport
|
||||
def parse(input: String): Either[String, ItemQuery] =
|
||||
def parse(input: String): Either[ParseFailure, ItemQuery] =
|
||||
if (input.isEmpty) Right(ItemQuery.all)
|
||||
else {
|
||||
val in = if (input.charAt(0) == '(') input else s"(& $input )"
|
||||
ExprParser
|
||||
.parseQuery(in)
|
||||
.left
|
||||
.map(pe => s"Error parsing: '$input': $pe") //TODO
|
||||
.map(ParseFailure.fromError(in))
|
||||
.map(q => q.copy(expr = ExprUtil.reduce(q.expr)))
|
||||
}
|
||||
|
||||
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)
|
||||
query match {
|
||||
case Left(err) =>
|
||||
BadRequest(BasicResult(false, err))
|
||||
BadRequest(BasicResult(false, err.render))
|
||||
case Right(sq) =>
|
||||
for {
|
||||
items <- backend.itemSearch.findItems(cfg.maxNoteLength)(
|
||||
|
Loading…
x
Reference in New Issue
Block a user