Skip to content

Commit ae49ce7

Browse files
committed
fix: self-review — nanoflow validation parity, fast-path lookup, code cleanup
- Add nanoflows to allNames() for forward-reference detection in scripts - Add ValidateNanoflowBody so mxcli validate catches semantic errors in nanoflow bodies - Extract validateFlowBody helper to deduplicate micro/nanoflow validation - Add GetRawUnitByName fast path to lookupNanoflowReturnType - Fix doc comment on addTransformJsonAction - Collapse 11 workflow stmt denylist cases into multi-type case
1 parent 06c519a commit ae49ce7

5 files changed

Lines changed: 46 additions & 29 deletions

File tree

mdl/executor/cmd_microflows_builder.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,15 @@ func (fb *flowBuilder) lookupMicroflowReturnType(qualifiedName string) microflow
145145
return nil
146146
}
147147

148+
// Fast path: direct lookup by qualified name avoids O(n) module walk.
149+
// Falls through to module walk on any error (not found, corrupt BSON, etc.).
148150
if rawUnit, err := fb.backend.GetRawUnitByName("microflow", qualifiedName); err == nil && rawUnit != nil && len(rawUnit.Contents) > 0 {
149151
if mf, err := fb.backend.ParseMicroflowBSON(rawUnit.Contents, model.ID(rawUnit.ID), ""); err == nil && mf != nil {
150152
return mf.ReturnType
151153
}
152154
}
153155

156+
// Slow path: enumerate all microflows in the module and match by name.
154157
moduleName, microflowName, ok := strings.Cut(qualifiedName, ".")
155158
if !ok || moduleName == "" || microflowName == "" {
156159
return nil
@@ -190,6 +193,15 @@ func (fb *flowBuilder) lookupNanoflowReturnType(qualifiedName string) microflows
190193
return nil
191194
}
192195

196+
// Fast path: direct lookup by qualified name avoids O(n) module walk.
197+
// Falls through to module walk on any error (not found, corrupt BSON, etc.).
198+
if rawUnit, err := fb.backend.GetRawUnitByName("nanoflow", qualifiedName); err == nil && rawUnit != nil && len(rawUnit.Contents) > 0 {
199+
if nf, err := fb.backend.ParseMicroflowBSON(rawUnit.Contents, model.ID(rawUnit.ID), ""); err == nil && nf != nil {
200+
return nf.ReturnType
201+
}
202+
}
203+
204+
// Slow path: enumerate all nanoflows in the module and match by name.
193205
moduleName, nanoflowName, ok := strings.Cut(qualifiedName, ".")
194206
if !ok || moduleName == "" || nanoflowName == "" {
195207
return nil

mdl/executor/cmd_microflows_builder_calls.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1268,7 +1268,7 @@ func (fb *flowBuilder) addImportFromMappingAction(s *ast.ImportFromMappingStmt)
12681268
return activity.ID
12691269
}
12701270

1271-
// addExportToMappingAction adds an ExportXmlAction to the microflow.
1271+
// addTransformJsonAction adds a TransformJsonAction to the microflow.
12721272
func (fb *flowBuilder) addTransformJsonAction(s *ast.TransformJsonStmt) model.ID {
12731273
activityX := fb.posX
12741274

mdl/executor/cmd_microflows_builder_validate.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,23 @@ import (
1212
// ValidateMicroflowBody validates the microflow body for semantic errors without building objects.
1313
// This is used by the check command to validate scripts without executing them.
1414
func ValidateMicroflowBody(s *ast.CreateMicroflowStmt) []string {
15-
// Build variable maps from parameters
15+
return validateFlowBody(s.Parameters, s.Body)
16+
}
17+
18+
// ValidateNanoflowBody validates the nanoflow body for semantic errors without building objects.
19+
// This is used by the check command to validate scripts without executing them.
20+
func ValidateNanoflowBody(s *ast.CreateNanoflowStmt) []string {
21+
return validateFlowBody(s.Parameters, s.Body)
22+
}
23+
24+
// validateFlowBody validates parameters and body statements for semantic errors.
25+
func validateFlowBody(params []ast.MicroflowParam, body []ast.MicroflowStatement) []string {
1626
varTypes := make(map[string]string)
1727
declaredVars := make(map[string]string)
1828

1929
var paramErrors []string
20-
for _, p := range s.Parameters {
30+
for _, p := range params {
2131
if p.Type.EntityRef != nil {
22-
// Reject bare entity names (empty module) — e.g., "Object" instead of "System.Workflow"
2332
if p.Type.EntityRef.Module == "" {
2433
paramErrors = append(paramErrors, fmt.Sprintf(
2534
"parameter '$%s': entity type '%s' is missing module prefix (use 'Module.%s')",
@@ -33,23 +42,20 @@ func ValidateMicroflowBody(s *ast.CreateMicroflowStmt) []string {
3342
varTypes[p.Name] = entityQN
3443
}
3544
} else {
36-
// Primitive type parameters
3745
declaredVars[p.Name] = p.Type.Kind.String()
3846
}
3947
}
4048
if len(paramErrors) > 0 {
4149
return paramErrors
4250
}
4351

44-
// Create a validation-only flow builder
4552
fb := &flowBuilder{
4653
varTypes: varTypes,
4754
declaredVars: declaredVars,
4855
errors: []string{},
4956
}
5057

51-
// Validate the body statements
52-
fb.validateStatements(s.Body)
58+
fb.validateStatements(body)
5359

5460
return fb.errors
5561
}

mdl/executor/nanoflow_validation.go

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -70,27 +70,18 @@ func checkDisallowedNanoflowAction(stmt ast.MicroflowStatement) string {
7070
return "export mapping is not allowed in nanoflows"
7171
case *ast.TransformJsonStmt:
7272
return "JSON transformation is not allowed in nanoflows"
73-
case *ast.CallWorkflowStmt:
74-
return "workflow calls are not allowed in nanoflows"
75-
case *ast.GetWorkflowDataStmt:
76-
return "workflow actions are not allowed in nanoflows"
77-
case *ast.GetWorkflowsStmt:
78-
return "workflow actions are not allowed in nanoflows"
79-
case *ast.GetWorkflowActivityRecordsStmt:
80-
return "workflow actions are not allowed in nanoflows"
81-
case *ast.WorkflowOperationStmt:
82-
return "workflow actions are not allowed in nanoflows"
83-
case *ast.SetTaskOutcomeStmt:
84-
return "workflow actions are not allowed in nanoflows"
85-
case *ast.OpenUserTaskStmt:
86-
return "workflow actions are not allowed in nanoflows"
87-
case *ast.NotifyWorkflowStmt:
88-
return "workflow actions are not allowed in nanoflows"
89-
case *ast.OpenWorkflowStmt:
90-
return "workflow actions are not allowed in nanoflows"
91-
case *ast.LockWorkflowStmt:
92-
return "workflow actions are not allowed in nanoflows"
93-
case *ast.UnlockWorkflowStmt:
73+
// Workflow actions — all server-side only
74+
case *ast.CallWorkflowStmt,
75+
*ast.GetWorkflowDataStmt,
76+
*ast.GetWorkflowsStmt,
77+
*ast.GetWorkflowActivityRecordsStmt,
78+
*ast.WorkflowOperationStmt,
79+
*ast.SetTaskOutcomeStmt,
80+
*ast.OpenUserTaskStmt,
81+
*ast.NotifyWorkflowStmt,
82+
*ast.OpenWorkflowStmt,
83+
*ast.LockWorkflowStmt,
84+
*ast.UnlockWorkflowStmt:
9485
return "workflow actions are not allowed in nanoflows"
9586
case *ast.DownloadFileStmt:
9687
return "file downloads are not allowed in nanoflows"

mdl/executor/validate.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ func (sc *scriptContext) allNames() []string {
130130
for n := range sc.microflows {
131131
names = append(names, n)
132132
}
133+
for n := range sc.nanoflows {
134+
names = append(names, n)
135+
}
133136
for n := range sc.pages {
134137
names = append(names, n)
135138
}
@@ -288,6 +291,11 @@ func validateWithContext(ctx *ExecContext, stmt ast.Statement, sc *scriptContext
288291
return mdlerrors.NewNotFound("module", s.Name.Module)
289292
}
290293
}
294+
// Validate nanoflow body for semantic errors (e.g., undeclared variables)
295+
if validationErrors := ValidateNanoflowBody(s); len(validationErrors) > 0 {
296+
return mdlerrors.NewValidationf("nanoflow '%s' has validation errors:\n - %s",
297+
s.Name.String(), strings.Join(validationErrors, "\n - "))
298+
}
291299
// Validate references inside nanoflow body (skip excluded nanoflows)
292300
if !s.Excluded {
293301
if refErrors := validateFlowBodyReferences(ctx, s.Body, sc); len(refErrors) > 0 {

0 commit comments

Comments
 (0)