mirror of
				https://github.com/TheAnachronism/docspell.git
				synced 2025-10-31 17:50: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 { | ||||
|  | ||||
|   @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)( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user