<tool_call>
edit({
"action": "create",
"path": "src/main/scala/rulekit/Rule.scala",
"overwrite": true,
"content": <<EOF
package rulekit
sealed trait Rule
final case class Predicate(value: String) extends Rule
final case class AllOf(rules: List[Rule]) extends Rule
final case class AnyOf(rules: List[Rule]) extends Rule
final case class NoneOf(rule: Rule) extends Rule
EOF
}
</tool_call>
<tool_call>
edit({
"action": "create",
"path": "src/main/scala/rulekit/RuleSerializer.scala",
"overwrite": true,
"content": <<EOF
package rulekit
import scala.util.matching.Regex
object RuleSerializer {
/** Convert a Rule to a simple JSON string. */
def toJson(rule: Rule): String = rule match {
case Predicate(v) =>
s"""{"type":"Predicate","value":${escapeString(v)}}"""
case AllOf(rs) =>
val inner = rs.map(toJson).mkString(",")
s"""{"type":"AllOf","rules":[$inner]}"""
case AnyOf(rs) =>
val inner = rs.map(toJson).mkString(",")
s"""{"type":"AnyOf","rules":[$inner]}"""
case NoneOf(r) =>
s"""{"type":"NoneOf","rule":${toJson(r)}}"""
}
/** Parse a JSON string produced by toJson back into a Rule. */
def fromJson(json: String): Rule = {
// simple helpers
def extractField(name: String, src: String): Option[String] = {
val pattern = new Regex(s""""$name":"([^"]*)"""")
pattern.findFirstMatchIn(src).map(_.group(1))
}
def extractObjectField(name: String, src: String): Option[String] = {
// matches "name":{...}
val pattern = new Regex(s""""$name":\\{")
val start = pattern.findFirstMatchIn(src).map(_.end - 1)
start.flatMap { idx =>
var depth = 0
var i = idx
while (i < src.length && (depth > 0 || src(i) != '}')) {
if (src(i) == '{') depth += 1
else if (src(i) == '}') depth -= 1
i += 1
}
if (i < src.length) Some(src.substring(idx, i + 1)) else None
}
}
def extractArrayField(name: String, src: String): List[String] = {
// matches "name":[ ... ]
val pattern = new Regex(s""""$name":\\[")
pattern.findFirstMatchIn(src) match {
case None => Nil
case Some(m) =>
var i = m.end
var depth = 0
val sb = new StringBuilder
while (i < src.length && (depth > 0 || src(i) != ']')) {
val ch = src(i)
if (ch == '[') depth += 1
else if (ch == ']') depth -= 1
sb.append(ch)
i += 1
}
// now sb contains the inner content (may be empty)
splitTopLevel(sb.toString())
}
}
def splitTopLevel(s: String): List[String] = {
// split by commas that are not inside braces or brackets
val res = scala.collection.mutable.ListBuffer.empty[String]
var depthBrace = 0
var depthBracket = 0
var start = 0
for (i <- 0 until s.length) {
s(i) match {
case '{' => depthBrace += 1
case '}' => depthBrace -= 1
case '[' => depthBracket += 1
case ']' => depthBracket -= 1
case ',' if depthBrace == 0 && depthBracket == 0 =>
res += s.substring(start, i).trim
start = i + 1
case _ => ()
}
}
if (start < s.length) res += s.substring(start).trim
res.filter(_.nonEmpty).toList
}
def unescapeString(s: String): String = {
// simple unescape for our escaped strings
s.replace("\\\"", "\"").replace("\\\\", "\\")
}
// Determine type
extractField("type", json) match {
case Some("Predicate") =>
val v = extractField("value", json).getOrElse("")
Predicate(unescapeString(v))
case Some("AllOf") =>
val elems = extractArrayField("rules", json)
AllOf(elems.map(fromJson))
case Some("AnyOf") =>
val elems = extractArrayField("rules", json)
AnyOf(elems.map(fromJson))
case Some("NoneOf") =>
extractObjectField("rule", json) match {
case Some(inner) => NoneOf(fromJson(inner))
case None => throw new IllegalArgumentException("Invalid NoneOf JSON")
}
case Some(other) =>
throw new IllegalArgumentException(s"Unknown rule type: $other")
case None =>
throw new IllegalArgumentException("No type field found")
}
}
private def escapeString(s: String): String = {
// Very simple escaping for double quotes and backslashes
val escaped = s.replace("\\", "\\\\").replace("\"", "\\\"")
s"\"$escaped\""
}
}
EOF
}
</tool_call>
<tool_call>
edit({
"action": "create",
"path": "src/test/scala/rulekit/RuleSerializerSpec.scala",
"overwrite": true,
"content": <<EOF
package rulekit
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
class RuleSerializerSpec extends AnyFunSuite with Matchers {
test("Predicate round‑trip serialization") {
val rule = Predicate("test")
val json = RuleSerializer.toJson(rule)
val parsed = RuleSerializer.fromJson(json)
parsed shouldBe rule
}
test("AllOf round‑trip serialization") {
val rule = AllOf(List(Predicate("a"), Predicate("b")))
val json = RuleSerializer.toJson(rule)
val parsed = RuleSerializer.fromJson(json)
parsed shouldBe rule
}
test("AnyOf round‑trip serialization") {
val rule = AnyOf(List(Predicate("x"), Predicate("y"), Predicate("z")))
val json = RuleSerializer.toJson(rule)
val parsed = RuleSerializer.fromJson(json)
parsed shouldBe rule
}
test("NoneOf round‑trip serialization") {
val rule = NoneOf(Predicate("solo"))
val json = RuleSerializer.toJson(rule)
val parsed = RuleSerializer.fromJson(json)
parsed shouldBe rule
}
test("Nested structures round‑trip") {
val rule = AllOf(List(
Predicate("p1"),
AnyOf(List(
Predicate("p2"),
NoneOf(AllOf(List(Predicate("p3"), Predicate("p4"))))
),
Predicate("p5")
))
val json = RuleSerializer.toJson(rule)
val parsed = RuleSerializer.fromJson(json)
parsed shouldBe rule
}
}
EOF
}
</tool_call>
<tool_call>
run({
"intent": "test",
"filter": "",
"reason": "Run all tests to verify serialization"
})
</tool_call>