refactor: extract shared Texts, Jsons, and SqlBindings helpers

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
0264408
2026-09-18 11:20:34 +08:00
co-authored by Cursor
parent 1f6e33c14b
commit 0e9bb4bd16
25 changed files with 189 additions and 176 deletions
@@ -8,18 +8,12 @@ public record ActionExecution(String id, String instanceId, String stepId, Strin
ActionExecutionStatus status, String errorMessage, Instant startedAt,
Instant finishedAt) {
public ActionExecution {
requireText(id, "id");
requireText(instanceId, "instanceId");
requireText(stepId, "stepId");
requireText(actionKey, "actionKey");
Texts.requireText(id, "id");
Texts.requireText(instanceId, "instanceId");
Texts.requireText(stepId, "stepId");
Texts.requireText(actionKey, "actionKey");
Objects.requireNonNull(status, "status must not be null");
Objects.requireNonNull(startedAt, "startedAt must not be null");
errorMessage = errorMessage == null || errorMessage.isBlank() ? null : errorMessage.strip();
}
private static void requireText(String value, String name) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException(name + " must not be blank");
}
errorMessage = Texts.blankToNull(errorMessage);
}
}
@@ -12,8 +12,8 @@ import java.util.Set;
public record ApprovalStep(String id, String name, List<String> candidates, ApprovalPolicy policy, StepKind kind,
String actionKey, StepDue due) {
public ApprovalStep {
requireText(id, "step id");
requireText(name, "step name");
Texts.requireText(id, "step id");
Texts.requireText(name, "step name");
kind = kind == null ? StepKind.APPROVAL : kind;
Objects.requireNonNull(policy, "policy must not be null");
Objects.requireNonNull(candidates, "candidates must not be null");
@@ -22,7 +22,7 @@ public record ApprovalStep(String id, String name, List<String> candidates, Appr
if (!candidates.isEmpty()) {
throw new IllegalArgumentException("an action step must not have candidates");
}
requireText(actionKey, "action key");
Texts.requireText(actionKey, "action key");
actionKey = actionKey.strip();
if (due != null) {
throw new IllegalArgumentException("an action step must not have due");
@@ -37,7 +37,7 @@ public record ApprovalStep(String id, String name, List<String> candidates, Appr
}
Set<String> distinct = new HashSet<>();
for (String candidate : candidates) {
requireText(candidate, "candidate");
Texts.requireText(candidate, "candidate");
if (!distinct.add(candidate)) {
throw new IllegalArgumentException("duplicate candidate in step " + id + ": " + candidate);
}
@@ -62,10 +62,4 @@ public record ApprovalStep(String id, String name, List<String> candidates, Appr
public static ApprovalStep action(String id, String name, String actionKey) {
return new ApprovalStep(id, name, List.of(), ApprovalPolicy.ANY, StepKind.ACTION, actionKey);
}
static void requireText(String value, String field) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException(field + " must not be blank");
}
}
}
@@ -0,0 +1,14 @@
package com.jetlumen.ordo.api;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
/** Shared Jackson mappers. */
public final class Jsons {
public static final ObjectMapper MAPPER = new ObjectMapper();
public static final ObjectMapper NON_NULL = new ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
private Jsons() {
}
}
@@ -29,9 +29,9 @@ public interface OrdoCatalog {
record Param(String name, String type, String label) {
public Param {
ApprovalStep.requireText(name, "param name");
ApprovalStep.requireText(type, "param type");
ApprovalStep.requireText(label, "param label");
Texts.requireText(name, "param name");
Texts.requireText(type, "param type");
Texts.requireText(label, "param label");
type = type.strip().toLowerCase(Locale.ROOT);
if (!PARAM_TYPES.contains(type)) {
throw new IllegalArgumentException("param type must be number, string or boolean");
@@ -11,11 +11,11 @@ import java.util.Set;
public record ProcessDefinition(String id, int version, String name, List<ApprovalStep> steps,
List<StepTransition> transitions) {
public ProcessDefinition {
ApprovalStep.requireText(id, "definition id");
Texts.requireText(id, "definition id");
if (version < 0) {
throw new IllegalArgumentException("definition version must not be negative");
}
ApprovalStep.requireText(name, "definition name");
Texts.requireText(name, "definition name");
steps = List.copyOf(steps);
if (steps.isEmpty()) {
throw new IllegalArgumentException("a definition must contain at least one step");
@@ -1,6 +1,5 @@
package com.jetlumen.ordo.api;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
@@ -17,8 +16,7 @@ import java.util.Objects;
/** Parses a structural JSON process graph into a {@link ProcessDefinition}. */
public final class ProcessDefinitionParser {
private static final ObjectMapper MAPPER = new ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
private static final ObjectMapper MAPPER = Jsons.NON_NULL;
private ProcessDefinitionParser() {
}
@@ -7,23 +7,13 @@ import java.util.Objects;
public record ProcessEvent(String id, String instanceId, String taskId, String stepId, ProcessEventType type,
String actor, String detail, Instant occurredAt) {
public ProcessEvent {
requireText(id, "id");
requireText(instanceId, "instanceId");
Texts.requireText(id, "id");
Texts.requireText(instanceId, "instanceId");
Objects.requireNonNull(type, "type must not be null");
Objects.requireNonNull(occurredAt, "occurredAt must not be null");
taskId = blankToNull(taskId);
stepId = blankToNull(stepId);
actor = blankToNull(actor);
detail = blankToNull(detail);
}
private static String blankToNull(String value) {
return value == null || value.isBlank() ? null : value.strip();
}
private static void requireText(String value, String name) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException(name + " must not be blank");
}
taskId = Texts.blankToNull(taskId);
stepId = Texts.blankToNull(stepId);
actor = Texts.blankToNull(actor);
detail = Texts.blankToNull(detail);
}
}
@@ -26,14 +26,14 @@ public sealed interface RoutingPredicate permits RoutingPredicate.Compare, Routi
record Compare(Op op, String var, Object value) implements RoutingPredicate {
public Compare {
Objects.requireNonNull(op, "op must not be null");
ApprovalStep.requireText(var, "var");
Texts.requireText(var, "var");
value = requireLiteral(value, "value");
}
}
record In(String var, List<Object> values) implements RoutingPredicate {
public In {
ApprovalStep.requireText(var, "var");
Texts.requireText(var, "var");
Objects.requireNonNull(values, "values must not be null");
if (values.isEmpty()) {
throw new IllegalArgumentException("in values must not be empty");
@@ -15,7 +15,7 @@ import java.util.Objects;
* {@link RoutingCondition} reference with arguments.
*/
public sealed interface RoutingWhen permits RoutingWhen.Predicate, RoutingWhen.Ref {
ObjectMapper MAPPER = new ObjectMapper();
ObjectMapper MAPPER = Jsons.MAPPER;
record Predicate(RoutingPredicate tree) implements RoutingWhen {
public Predicate {
@@ -25,7 +25,7 @@ public sealed interface RoutingWhen permits RoutingWhen.Predicate, RoutingWhen.R
record Ref(String key, Map<String, Object> args) implements RoutingWhen {
public Ref {
ApprovalStep.requireText(key, "ref");
Texts.requireText(key, "ref");
args = Map.copyOf(args == null ? Map.of() : args);
}
}
@@ -11,8 +11,8 @@ public record StepDue(Duration after, DueThen then, String to, String action) {
throw new IllegalArgumentException("due after must be positive");
}
Objects.requireNonNull(then, "due then must not be null");
to = blankToNull(to);
action = blankToNull(action);
to = Texts.blankToNull(to);
action = Texts.blankToNull(action);
switch (then) {
case REASSIGN -> {
if (to == null) {
@@ -50,7 +50,4 @@ public record StepDue(Duration after, DueThen then, String to, String action) {
return new StepDue(after, DueThen.GOTO, stepId, null);
}
private static String blankToNull(String value) {
return value == null || value.isBlank() ? null : value.strip();
}
}
@@ -7,7 +7,7 @@ package com.jetlumen.ordo.api;
*/
public record StepTransition(String fromStepId, String toStepId, RoutingWhen when, int priority) {
public StepTransition {
ApprovalStep.requireText(fromStepId, "fromStepId");
Texts.requireText(fromStepId, "fromStepId");
if (toStepId != null && toStepId.isBlank()) {
throw new IllegalArgumentException("toStepId must not be blank when present");
}
@@ -6,10 +6,8 @@ import java.util.Objects;
/** Immutable audit record created when an approval task is completed. */
public record TaskAction(String actor, String comment, Instant operatedAt) {
public TaskAction {
if (actor == null || actor.isBlank()) {
throw new IllegalArgumentException("actor must not be blank");
}
comment = comment == null || comment.isBlank() ? null : comment.strip();
Texts.requireText(actor, "actor");
comment = Texts.blankToNull(comment);
Objects.requireNonNull(operatedAt, "operatedAt must not be null");
}
}
@@ -0,0 +1,17 @@
package com.jetlumen.ordo.api;
/** Shared blank-string checks used by domain records and the engine. */
public final class Texts {
private Texts() {
}
public static void requireText(String value, String name) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException(name + " must not be blank");
}
}
public static String blankToNull(String value) {
return value == null || value.isBlank() ? null : value.strip();
}
}