116 lines
4.3 KiB
Go
116 lines
4.3 KiB
Go
package workflowdef
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
var officialDocCounts = map[string]int{
|
|
OfficialKeyMain: 7,
|
|
OfficialKeyProductManager: 1,
|
|
OfficialKeyTestManager: 1,
|
|
OfficialKeyRDev: 60,
|
|
}
|
|
|
|
// keyValidator checks a registry key (executor / quality gate). Injected so workflowdef
|
|
// does not import the concrete workflow registry package.
|
|
type keyValidator func(key string) error
|
|
|
|
// ValidateDefinitionV2 schema v2 发布校验。
|
|
// execValidate / gateValidate are optional ports; nil uses the local registry snapshot
|
|
// (so workflowdef never imports internal/workflow).
|
|
func ValidateDefinitionV2(def VersionDefinition, execValidate, gateValidate keyValidator) ValidationResult {
|
|
if execValidate == nil {
|
|
execValidate = defaultValidateExecutorKey
|
|
}
|
|
if gateValidate == nil {
|
|
gateValidate = defaultValidateQualityGateKey
|
|
}
|
|
issues := make([]ValidationIssue, 0)
|
|
issues = append(issues, stageGatingIssues(def)...)
|
|
if def.SchemaVersion < 2 {
|
|
issues = append(issues, ValidationIssue{Code: "SCHEMA_VERSION", Message: "schema_version must be >= 2"})
|
|
}
|
|
if def.RequiredUpstreamWorkflow != "" {
|
|
issues = append(issues, ValidationIssue{
|
|
Code: "UPSTREAM_NOT_ALLOWED", Message: "正式工作流不得声明 required_upstream_workflow",
|
|
})
|
|
}
|
|
seenPos := map[int]bool{}
|
|
seenTask := map[string]bool{}
|
|
docCount := 0
|
|
for _, st := range def.Stages {
|
|
if st.Key == "" {
|
|
issues = append(issues, ValidationIssue{Code: "STAGE_KEY_REQUIRED", Message: "stage key required"})
|
|
continue
|
|
}
|
|
if st.Position <= 0 {
|
|
issues = append(issues, ValidationIssue{Code: "STAGE_POSITION", Field: st.Key, Message: "position must be > 0"})
|
|
}
|
|
if seenPos[st.Position] {
|
|
issues = append(issues, ValidationIssue{Code: "DUPLICATE_POSITION", Field: st.Key, Message: "duplicate stage position"})
|
|
}
|
|
seenPos[st.Position] = true
|
|
if len(st.DocTasks) == 0 {
|
|
issues = append(issues, ValidationIssue{Code: "STAGE_NO_DOC_TASKS", Field: st.Key, Message: "each stage must have at least one doc task"})
|
|
}
|
|
for _, dt := range st.DocTasks {
|
|
docCount++
|
|
if dt.Key == "" {
|
|
issues = append(issues, ValidationIssue{Code: "DOC_TASK_KEY_REQUIRED", Message: "doc task key required"})
|
|
continue
|
|
}
|
|
if seenTask[dt.Key] {
|
|
issues = append(issues, ValidationIssue{Code: "DUPLICATE_DOC_TASK", Field: dt.Key, Message: "duplicate doc task key"})
|
|
}
|
|
seenTask[dt.Key] = true
|
|
if dt.ExecutorKey != "" {
|
|
if err := execValidate(dt.ExecutorKey); err != nil {
|
|
issues = append(issues, ValidationIssue{Code: "UNKNOWN_EXECUTOR", Field: dt.Key, Message: err.Error()})
|
|
}
|
|
}
|
|
if dt.QualityGateKey != "" {
|
|
if err := gateValidate(dt.QualityGateKey); err != nil {
|
|
issues = append(issues, ValidationIssue{Code: "UNKNOWN_QUALITY_GATE", Field: dt.Key, Message: err.Error()})
|
|
}
|
|
}
|
|
if dt.OutputDocType == "" {
|
|
issues = append(issues, ValidationIssue{Code: "OUTPUT_DOC_TYPE", Field: dt.Key, Message: "output_doc_type required"})
|
|
}
|
|
}
|
|
}
|
|
if len(def.Stages) == 0 {
|
|
issues = append(issues, ValidationIssue{Code: "NO_STAGES", Message: "at least one stage required"})
|
|
}
|
|
// 固定文档数约束医疗正式工作流;软件工作流按自身 manifest 完整性校验。
|
|
if def.WorkflowKey != "" && IsMedicalWorkflowKey(def.WorkflowKey) {
|
|
if want, ok := officialDocCounts[def.WorkflowKey]; ok && docCount != want {
|
|
issues = append(issues, ValidationIssue{
|
|
Code: "OFFICIAL_DOC_COUNT", Field: def.WorkflowKey,
|
|
Message: fmt.Sprintf("official workflow %s requires exactly %d doc tasks, got %d", def.WorkflowKey, want, docCount),
|
|
})
|
|
}
|
|
}
|
|
return ValidationResult{Valid: len(issues) == 0, Issues: issues}
|
|
}
|
|
|
|
// ValidateVersionBySchema 按 schema_version 路由校验器。
|
|
func ValidateVersionBySchema(def VersionDefinition, execValidate, gateValidate keyValidator) ValidationResult {
|
|
if def.SchemaVersion >= 2 {
|
|
return ValidateDefinitionV2(def, execValidate, gateValidate)
|
|
}
|
|
return validateDefinitionV1(def)
|
|
}
|
|
|
|
func validateDefinitionV1(def VersionDefinition) ValidationResult {
|
|
issues := make([]ValidationIssue, 0)
|
|
for i, st := range def.Stages {
|
|
if st.Position != i {
|
|
issues = append(issues, ValidationIssue{
|
|
Code: "STAGE_POSITION", Field: st.Key,
|
|
Message: fmt.Sprintf("position must equal index %d", i),
|
|
})
|
|
}
|
|
}
|
|
return ValidationResult{Valid: len(issues) == 0, Issues: issues}
|
|
}
|