Skip to content

Commit

Permalink
Merge pull request #717 from camunda/713-reable-failure-messages
Browse files Browse the repository at this point in the history
feat: Make failure messages more readable
  • Loading branch information
saig0 authored Sep 20, 2023
2 parents 82b19de + 269a60f commit fc7b2cf
Show file tree
Hide file tree
Showing 12 changed files with 382 additions and 157 deletions.
11 changes: 11 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,17 @@
<differenceType>7009</differenceType>
<method>* validator()</method>
</ignored>
<!-- ignore auto-generated Scala functions -->
<ignored>
<className>org/camunda/feel/syntaxtree/**</className>
<differenceType>7002</differenceType>
<method>scala.Function1 andThen(scala.Function1)</method>
</ignored>
<ignored>
<className>org/camunda/feel/syntaxtree/**</className>
<differenceType>7002</differenceType>
<method>scala.Function1 compose(scala.Function1)</method>
</ignored>
</ignored>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ object BuiltinFunction {
}

private def error: PartialFunction[List[Val], Any] = {
case vars if (vars.exists(_.isInstanceOf[ValError])) =>
vars.filter(_.isInstanceOf[ValError]).head.asInstanceOf[ValError]
case e => ValError(s"Illegal arguments: $e")
case args if (args.exists(_.isInstanceOf[ValError])) =>
args.filter(_.isInstanceOf[ValError]).head.asInstanceOf[ValError]
case args =>
val argumentList = args.map("'" + _ + "'").mkString(", ")
ValError(s"Illegal arguments: $argumentList")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -194,31 +194,10 @@ object ConversionBuiltinFunctions {
private def isValidDecimalSeparator(separator: String) =
separator == "," || separator == "."

private lazy val dateTimeOffsetZoneIdPattern =
Pattern.compile("(.*)([+-]\\d{2}:\\d{2}|Z)(@.*)")

private def stringFunction = builtinFunction(
params = List("from"),
invoke = {
case List(ValString(from)) => ValString(from)
case List(ValBoolean(from)) => ValString(from.toString)
case List(ValNumber(from)) => ValString(from.toString)
case List(ValDate(from)) => ValString(from.format(dateFormatter))
case List(ValLocalTime(from)) =>
ValString(from.format(localTimeFormatter))
case List(ValTime(from)) => ValString(from.format)
case List(ValLocalDateTime(from)) =>
ValString(from.format(localDateTimeFormatter))
case List(ValDateTime(from)) => {
val formattedDateTime = from.format(dateTimeFormatter)
// remove offset-id if zone-id is present
val dateTimeWithOffsetOrZoneId = dateTimeOffsetZoneIdPattern
.matcher(formattedDateTime)
.replaceAll("$1$3")
ValString(dateTimeWithOffsetOrZoneId)
}
case List(duration: ValYearMonthDuration) => ValString(duration.toString)
case List(duration: ValDayTimeDuration) => ValString(duration.toString)
case List(from) => ValString(from.toString)
}
)

Expand Down
104 changes: 44 additions & 60 deletions src/main/scala/org/camunda/feel/impl/interpreter/FeelInterpreter.scala

Large diffs are not rendered by default.

154 changes: 108 additions & 46 deletions src/main/scala/org/camunda/feel/syntaxtree/Val.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
package org.camunda.feel.syntaxtree

import org.camunda.feel.context.Context
import org.camunda.feel.{Date, DateTime, DayTimeDuration, LocalDateTime, LocalTime, Number, Time, YearMonthDuration}
import org.camunda.feel.{Date, DateTime, DayTimeDuration, LocalDateTime, LocalTime, Number, Time, YearMonthDuration, dateTimeFormatter, localDateTimeFormatter, localTimeFormatter}

import java.math.BigInteger
import java.time.Duration
import java.util.regex.Pattern

/**
* FEEL supports the following datatypes:
Expand Down Expand Up @@ -100,11 +100,17 @@ sealed trait Val extends Ordered[Val] {

}

case class ValNumber(value: Number) extends Val
case class ValNumber(value: Number) extends Val {
override def toString: String = value.toString()
}

case class ValBoolean(value: Boolean) extends Val
case class ValBoolean(value: Boolean) extends Val {
override def toString: String = value.toString
}

case class ValString(value: String) extends Val
case class ValString(value: String) extends Val {
override def toString: String = s"\"$value\""
}

case class ValDate(value: Date) extends Val {
override protected val properties: Map[String, Val] = Map(
Expand All @@ -113,6 +119,8 @@ case class ValDate(value: Date) extends Val {
"day" -> ValNumber(value.getDayOfMonth),
"weekday" -> ValNumber(value.getDayOfWeek.getValue)
)

override def toString: String = value.toString
}

case class ValLocalTime(value: LocalTime) extends Val {
Expand All @@ -123,6 +131,8 @@ case class ValLocalTime(value: LocalTime) extends Val {
"time offset" -> ValNull,
"timezone" -> ValNull
)

override def toString: String = value.format(localTimeFormatter)
}

case class ValTime(value: Time) extends Val {
Expand All @@ -134,6 +144,8 @@ case class ValTime(value: Time) extends Val {
ValDayTimeDuration(Duration.ofSeconds(value.getOffsetInTotalSeconds)),
"timezone" -> value.getZoneId.map(ValString).getOrElse(ValNull)
)

override def toString: String = value.format
}

case class ValLocalDateTime(value: LocalDateTime) extends Val {
Expand All @@ -148,6 +160,8 @@ case class ValLocalDateTime(value: LocalDateTime) extends Val {
"time offset" -> ValNull,
"timezone" -> ValNull
)

override def toString: String = value.format(localDateTimeFormatter)
}

case class ValDateTime(value: DateTime) extends Val {
Expand All @@ -168,54 +182,73 @@ case class ValDateTime(value: DateTime) extends Val {
)

private def hasTimeZone = !value.getOffset.equals(value.getZone)

override def toString: String = ValDateTime.format(value)
}

object ValDateTime {

private val dateTimeOffsetZoneIdPattern = Pattern.compile("(.*)([+-]\\d{2}:\\d{2}|Z)(@.*)")

def format(value: DateTime): String = {
val formattedDateTime = value.format(dateTimeFormatter)
// remove offset-id if zone-id is present
dateTimeOffsetZoneIdPattern
.matcher(formattedDateTime)
.replaceAll("$1$3")
}

}

case class ValYearMonthDuration(value: YearMonthDuration) extends Val {

override def toString: String = {
def makeString(sign: String, year: Long, month: Long): String = {
val y = Option(year).filterNot(_ == 0).map(_ + "Y").getOrElse("")
val m = Option(month).filterNot(_ == 0).map(_ + "M").getOrElse("")
override def toString: String = ValYearMonthDuration.format(value)

val stringBuilder = new StringBuilder("")
stringBuilder.append(sign).append("P").append(y).append(m)
stringBuilder.toString()
}
override val properties: Map[String, Val] = Map(
"years" -> ValNumber(value.getYears),
"months" -> ValNumber(value.getMonths)
)
}

object ValYearMonthDuration {

def format(value: YearMonthDuration): String = {
val year = value.getYears
val month = value.getMonths % 12

if (year == 0 && month == 0)
"P0Y"
else if (year <= 0 && month <= 0)
makeString("-", -year, -month)
"-" + mkString(-year, -month)
else
makeString("", year, month)
mkString(year, month)
}

private def mkString(year: Long, month: Long): String = {
val y = Option(year).filterNot(_ == 0).map(_ + "Y").getOrElse("")
val m = Option(month).filterNot(_ == 0).map(_ + "M").getOrElse("")

val stringValue = new StringBuilder("P")
stringValue.append(y).append(m)
stringValue.toString()
}

}

case class ValDayTimeDuration(value: DayTimeDuration) extends Val {
override def toString: String = ValDayTimeDuration.format(value)

override val properties: Map[String, Val] = Map(
"years" -> ValNumber(value.getYears),
"months" -> ValNumber(value.getMonths)
"days" -> ValNumber(value.toDays),
"hours" -> ValNumber(value.toHours % 24),
"minutes" -> ValNumber(value.toMinutes % 60),
"seconds" -> ValNumber(value.getSeconds % 60)
)
}

case class ValDayTimeDuration(value: DayTimeDuration) extends Val {
override def toString: String = {
def makeString(sign: String, day: Long, hour: Long, minute: Long, second: Long): String = {
val d = Option(day).filterNot(_ == 0).map(_ + "D").getOrElse("")
val h = Option(hour).filterNot(_ == 0).map(_ + "H").getOrElse("")
val m = Option(minute).filterNot(_ == 0).map(_ + "M").getOrElse("")
val s = Option(second).filterNot(_ == 0).map(_ + "S").getOrElse("")

val stringBuilder = new StringBuilder("")
stringBuilder.append(sign).append("P").append(d)
if (h.nonEmpty || m.nonEmpty || s.nonEmpty) {
stringBuilder.append("T")
stringBuilder.append(h).append(m).append(s)
}
stringBuilder.toString()
}
object ValDayTimeDuration {

def format(value: DayTimeDuration): String = {
val day = value.toDays
val hour = value.toHours % 24
val minute = value.toMinutes % 60
Expand All @@ -224,32 +257,61 @@ case class ValDayTimeDuration(value: DayTimeDuration) extends Val {
if (day == 0 && hour == 0 && minute == 0 && second == 0)
"P0D"
else if (day <= 0 && hour <= 0 && minute <= 0 && second <= 0)
makeString("-", -day, -hour, -minute, -second)
"-" + mkString(-day, -hour, -minute, -second)
else
makeString("", day, hour, minute, second)
mkString(day, hour, minute, second)
}
override val properties: Map[String, Val] = Map(
"days" -> ValNumber(value.toDays),
"hours" -> ValNumber(value.toHours % 24),
"minutes" -> ValNumber(value.toMinutes % 60),
"seconds" -> ValNumber(value.getSeconds % 60)
)

private def mkString(day: Long, hour: Long, minute: Long, second: Long): String = {
val d = Option(day).filterNot(_ == 0).map(_ + "D").getOrElse("")
val h = Option(hour).filterNot(_ == 0).map(_ + "H").getOrElse("")
val m = Option(minute).filterNot(_ == 0).map(_ + "M").getOrElse("")
val s = Option(second).filterNot(_ == 0).map(_ + "S").getOrElse("")

val stringValue = new StringBuilder("P")
stringValue.append(d)
if (h.nonEmpty || m.nonEmpty || s.nonEmpty) {
stringValue.append("T")
stringValue.append(h).append(m).append(s)
}
stringValue.toString()
}

}

case class ValError(error: String) extends Val
case class ValError(error: String) extends Val {
override def toString: String = s"error(\"$error\")"
}

case object ValNull extends Val
case object ValNull extends Val {
override def toString: String = "null"
}

case class ValFunction(params: List[String],
invoke: List[Val] => Any,
hasVarArgs: Boolean = false)
extends Val {

val paramSet: Set[String] = params.toSet

override def toString: String = s"function(${params.mkString(", ")})"
}

case class ValContext(context: Context) extends Val
case class ValContext(context: Context) extends Val {
override def toString: String = context.variableProvider.getVariables
.map { case (key, value) => s"$key:$value" }
.mkString(start = "{", sep = ", ", end = "}")
}

case class ValList(items: List[Val]) extends Val
case class ValList(items: List[Val]) extends Val {
override def toString: String = items.mkString(start = "[", sep = ", ", end = "]")
}

case class ValRange(start: RangeBoundary, end: RangeBoundary) extends Val
case class ValRange(start: RangeBoundary, end: RangeBoundary) extends Val {
override def toString: String = {
val startSymbol = if (start.isClosed) "[" else "("
val endSymbol = if (end.isClosed) "]" else ")"

s"$startSymbol${start.value}..${end.value}$endSymbol"
}
}
Loading

0 comments on commit fc7b2cf

Please sign in to comment.