Java
rules-cascade-core for the JVM. No dependencies; works on maps and lists behind whatever JSON or YAML library the service already uses.
rules-cascade-core evaluates bundles and compiles source rulesets on any JVM. It has no
dependencies: it takes documents and requests as Map<String, Object>, so it sits behind Jackson,
Gson, SnakeYAML or anything else.
Supported versions: Java 17 or later (maven.compiler.release 17). CI tests JDK 17 and 21 on
Linux and JDK 21 on Windows and macOS, and builds the Spring Boot example. No maximum is declared.
Install
rules-cascade-core is not published to Maven Central yet; once it is, depend on it as below. Until
then, a JVM service can evaluate the same bundles through the signed rcas command over
the engine protocol: download and verify it, then follow
Evaluate from another language.
<dependency>
<groupId>com.rulescascade</groupId>
<artifactId>rules-cascade-core</artifactId>
<version>1.0.0-alpha.5</version>
</dependency>The package is com.rulescascade.
Load
Once, at start-up. A load error must stop the application.
// From a bundle compiled in CI: no YAML, no load-time checks repeated.
@SuppressWarnings("unchecked")
Map<String, Object> parsed = (Map<String, Object>) Json.parse(text);
RuleSet rules = RuleSet.fromBundle(parsed);
// Or compile source documents: document and registry are parsed YAML or JSON as maps.
RuleSet rules = RuleSet.load(document, registry, schemaLoader);registry maps ruleset ids to documents so extends can be resolved. schemaLoader returns the
document behind an entity's $ref; with null, PATH_UNKNOWN and SCHEMA_REF_UNRESOLVED are not
checked.
Custom operators are attached with withOperators, and the start-up check compares them with what
the manifest needs:
Map<String, CustomOperator> operators = Map.of(
"x-starts-with-zero", args -> args.get(0) instanceof String s && s.startsWith("0"));
RuleSet rules = RuleSet.load(document, registry, schemaLoader).withOperators(operators);
List<String> missing = new ArrayList<>(rules.requiredOperators(Channel.SERVER));
missing.removeAll(operators.keySet());
if (!missing.isEmpty()) {
throw new IllegalStateException("custom operators not registered: " + missing);
}Evaluate
EvaluationResult result = rules.evaluate(
EvaluationRequest.builder("Transfer", "create")
.data(payload) // Map<String, Object>
.actor(userId, roles) // from authentication
.resolutions(resolutionsFromRequest)
.build());
if (!result.allowed()) {
throw new RuleViolationException(result); // return result.toMap() as problem details
}
repository.save(transfer);
result.commands().forEach(outbox::publishOnce); // de-duplicate on command.idempotencyKey()rules.evaluate(request, Channel.CLIENT) evaluates the client channel. Evaluator evaluates a
manifest received from elsewhere. A ruleset that reads ctx.now needs
.ctx(Map.of("now", Instant.now().toString())) on the builder. RuleViolationException belongs to
the Spring Boot example (examples/backend-spring-boot/src/main/java/com/example/payments/RuleViolationException.java),
not to the library. It is a few lines; write your own the same way:
/** The operation was denied by one or more blocking findings. */
public class RuleViolationException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final transient EvaluationResult result;
public RuleViolationException(EvaluationResult result) {
super("Denied by " + result.blockingFindings().size() + " blocking finding(s)");
this.result = result;
}
public EvaluationResult result() {
return result;
}
}Errors
| What | How it surfaces | What to do |
|---|---|---|
| A bundle that is not format 1.x, or has no usable manifests | LoadException from RuleSet.fromBundle with BUNDLE_UNSUPPORTED or BUNDLE_INVALID | Let it stop start-up |
| A source ruleset that fails a check | LoadException; problems() and codes() say why | Let it stop start-up; fix it in CI |
| A request of the wrong shape | IllegalArgumentException from the builder (no entity or operation, roles that are not strings, a resolution without a rule); EvaluationRequest.fromMap applies the full wire check | Answer 400 |
| A rule that cannot be evaluated, or a missing operator | No exception: a blocking RULE-EVALUATION-ERROR finding | Alert on it |
More
The full API (bundles, places and data types, numbers, the engine protocol through Main engine)
is in the package README. A complete service is the
Spring Boot example.