diff --git a/docs/ordo-rest.openapi.yaml b/docs/ordo-rest.openapi.yaml index a9d9bf5..1e78a81 100644 --- a/docs/ordo-rest.openapi.yaml +++ b/docs/ordo-rest.openapi.yaml @@ -471,6 +471,7 @@ components: label: type: string ProcessDefinitionDocument: + description: Closed document shape is docs/process-definition.schema.json. Graph rules are enforced by ProcessDefinitionParser. type: object required: [id, name, steps, transitions] properties: diff --git a/docs/process-definition.schema.json b/docs/process-definition.schema.json new file mode 100644 index 0000000..918a29f --- /dev/null +++ b/docs/process-definition.schema.json @@ -0,0 +1,297 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://jetlumen.com/ordo/schema/process-definition.json", + "title": "Ordo process definition document", + "$comment": "Document shape for ProcessDefinitionParser.fromJson. Graph connectivity, unique ids, PARALLEL membership, due.goto branch rules, and predicate depth/leaf limits are enforced only by the parser.", + "type": "object", + "additionalProperties": false, + "required": ["id", "name", "steps", "transitions"], + "properties": { + "id": { "$ref": "#/$defs/nonEmptyString" }, + "name": { "$ref": "#/$defs/nonEmptyString" }, + "version": { + "type": "integer", + "minimum": 0, + "$comment": "Ignored by fromJson; written by toJson after publish." + }, + "startStep": { "$ref": "#/$defs/nonEmptyString" }, + "steps": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/topStep" } + }, + "transitions": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/transition" } + } + }, + "$defs": { + "nonEmptyString": { + "type": "string", + "pattern": ".*\\S.*" + }, + "literal": { + "type": ["string", "number", "boolean"] + }, + "topStep": { + "oneOf": [ + { "$ref": "#/$defs/approvalStep" }, + { "$ref": "#/$defs/actionStep" }, + { "$ref": "#/$defs/parallelStep" } + ] + }, + "leafStep": { + "oneOf": [ + { "$ref": "#/$defs/approvalStep" }, + { "$ref": "#/$defs/actionStep" } + ] + }, + "approvalStep": { + "type": "object", + "additionalProperties": false, + "required": ["id", "name", "candidates"], + "properties": { + "id": { "$ref": "#/$defs/nonEmptyString" }, + "name": { "$ref": "#/$defs/nonEmptyString" }, + "kind": { "const": "APPROVAL" }, + "candidates": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "$ref": "#/$defs/nonEmptyString" } + }, + "policy": { "type": "string", "enum": ["ANY", "ALL"] }, + "due": { "$ref": "#/$defs/due" } + } + }, + "actionStep": { + "type": "object", + "additionalProperties": false, + "required": ["id", "name", "kind", "action"], + "properties": { + "id": { "$ref": "#/$defs/nonEmptyString" }, + "name": { "$ref": "#/$defs/nonEmptyString" }, + "kind": { "const": "ACTION" }, + "action": { "$ref": "#/$defs/nonEmptyString" }, + "policy": { "type": "string", "enum": ["ANY", "ALL"] } + } + }, + "parallelStep": { + "type": "object", + "additionalProperties": false, + "required": ["id", "name", "kind", "branches"], + "properties": { + "id": { "$ref": "#/$defs/nonEmptyString" }, + "name": { "$ref": "#/$defs/nonEmptyString" }, + "kind": { "const": "PARALLEL" }, + "policy": { "type": "string", "enum": ["ANY", "ALL"] }, + "branches": { + "type": "array", + "minItems": 2, + "items": { "$ref": "#/$defs/branch" } + } + } + }, + "branch": { + "type": "object", + "additionalProperties": false, + "required": ["id", "steps", "transitions"], + "properties": { + "id": { "$ref": "#/$defs/nonEmptyString" }, + "steps": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/leafStep" } + }, + "transitions": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/transition" } + } + } + }, + "due": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["after", "then", "to"], + "properties": { + "after": { "$ref": "#/$defs/isoDuration" }, + "then": { "const": "reassign" }, + "to": { "$ref": "#/$defs/nonEmptyString" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["after", "then"], + "properties": { + "after": { "$ref": "#/$defs/isoDuration" }, + "then": { "const": "notify" }, + "action": { "$ref": "#/$defs/nonEmptyString" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["after", "then", "to"], + "properties": { + "after": { "$ref": "#/$defs/isoDuration" }, + "then": { "const": "goto" }, + "to": { "$ref": "#/$defs/nonEmptyString" } + } + } + ] + }, + "isoDuration": { + "type": "string", + "minLength": 2, + "$comment": "java.time.Duration.parse; must be positive after parse." + }, + "transition": { + "type": "object", + "additionalProperties": false, + "required": ["from"], + "properties": { + "from": { "$ref": "#/$defs/nonEmptyString" }, + "to": { + "type": ["string", "null"], + "minLength": 1 + }, + "when": { "$ref": "#/$defs/when" }, + "priority": { "type": "integer" } + } + }, + "when": { + "oneOf": [ + { "$ref": "#/$defs/refWhen" }, + { "$ref": "#/$defs/predicate" } + ] + }, + "refWhen": { + "type": "object", + "additionalProperties": false, + "required": ["ref"], + "properties": { + "ref": { "$ref": "#/$defs/nonEmptyString" }, + "args": { + "type": "object", + "additionalProperties": true + } + } + }, + "predicate": { + "oneOf": [ + { "$ref": "#/$defs/compareEq" }, + { "$ref": "#/$defs/compareNe" }, + { "$ref": "#/$defs/compareGt" }, + { "$ref": "#/$defs/compareGte" }, + { "$ref": "#/$defs/compareLt" }, + { "$ref": "#/$defs/compareLte" }, + { "$ref": "#/$defs/inPredicate" }, + { "$ref": "#/$defs/andPredicate" }, + { "$ref": "#/$defs/orPredicate" }, + { "$ref": "#/$defs/notPredicate" } + ] + }, + "comparePair": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "prefixItems": [ + { "$ref": "#/$defs/nonEmptyString" }, + { "$ref": "#/$defs/literal" } + ] + }, + "compareEq": { + "type": "object", + "additionalProperties": false, + "required": ["eq"], + "properties": { "eq": { "$ref": "#/$defs/comparePair" } } + }, + "compareNe": { + "type": "object", + "additionalProperties": false, + "required": ["ne"], + "properties": { "ne": { "$ref": "#/$defs/comparePair" } } + }, + "compareGt": { + "type": "object", + "additionalProperties": false, + "required": ["gt"], + "properties": { "gt": { "$ref": "#/$defs/comparePair" } } + }, + "compareGte": { + "type": "object", + "additionalProperties": false, + "required": ["gte"], + "properties": { "gte": { "$ref": "#/$defs/comparePair" } } + }, + "compareLt": { + "type": "object", + "additionalProperties": false, + "required": ["lt"], + "properties": { "lt": { "$ref": "#/$defs/comparePair" } } + }, + "compareLte": { + "type": "object", + "additionalProperties": false, + "required": ["lte"], + "properties": { "lte": { "$ref": "#/$defs/comparePair" } } + }, + "inPredicate": { + "type": "object", + "additionalProperties": false, + "required": ["in"], + "properties": { + "in": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "prefixItems": [ + { "$ref": "#/$defs/nonEmptyString" }, + { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/literal" } + } + ] + } + } + }, + "andPredicate": { + "type": "object", + "additionalProperties": false, + "required": ["and"], + "properties": { + "and": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/predicate" } + } + } + }, + "orPredicate": { + "type": "object", + "additionalProperties": false, + "required": ["or"], + "properties": { + "or": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/predicate" } + } + } + }, + "notPredicate": { + "type": "object", + "additionalProperties": false, + "required": ["not"], + "properties": { + "not": { "$ref": "#/$defs/predicate" } + } + } + } +} diff --git a/docs/usage.md b/docs/usage.md index b45fde2..9cc78eb 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -174,7 +174,7 @@ new ProcessDefinition("leave-request-routed", "Leave request", ### 3.2 JSON -`ProcessDefinitionParser.fromJson(String|InputStream)` / `toJson(ProcessDefinition)`。Spring 默认扫 `classpath*:ordo/*.json`。`toJson` 写出 `version` 与 `startStep`(当前步骤列表首位);`fromJson` 仍忽略 JSON 里的 `version`。 +`ProcessDefinitionParser.fromJson(String|InputStream)` / `toJson(ProcessDefinition)`。Spring 默认扫 `classpath*:ordo/*.json`。`toJson` 写出 `version` 与 `startStep`(当前步骤列表首位);`fromJson` 仍忽略 JSON 里的 `version`。文档外形:[process-definition.schema.json](process-definition.schema.json);图连通、PARALLEL 约束、谓词深度/叶子上限仍以解析器为准。 ```json {