Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Make failure messages more readable #717

Merged
merged 6 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
saig0 marked this conversation as resolved.
Show resolved Hide resolved
}
)

Expand Down

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