Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ const (
propRetryPolicy = "retryPolicy"
propTaskQueue = "taskQueue"
propType = "type"
propScheduleWorkflowType = "scheduleWorkflowType"
propScheduleWorkflowName = "scheduleWorkflowName"
propScheduleID = "scheduleId"
propScheduleInput = "scheduleInput"
propSeconds = "seconds"
Expand Down
11 changes: 5 additions & 6 deletions definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,12 +413,11 @@ var documentMetadataDefinition = &jsonschema.Schema{
Title: "ContinueAsNewMaxHistoryLength",
Description: "Allows you to test the Continue-As-New functionality by specifying the max history length before triggering.",
},
propScheduleWorkflowType: {
Type: typeString,
Title: "ScheduleWorkflowType",
Description: "The workflow type the schedule starts: the document.workflowType or a top-level Do task. " +
"Defaults to document.workflowType; required when every top-level task is a Do task.",
MinLength: utils.Ptr(1),
propScheduleWorkflowName: {
Type: typeString,
Title: "ScheduleWorkflowName",
Description: "Set the workflow name to trigger - this will either be the document.workflowType or the Do task",
MinLength: utils.Ptr(1),
},
propScheduleID: {
Type: typeString,
Expand Down
18 changes: 6 additions & 12 deletions metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func TestDocumentMetadataDefinitionShape(t *testing.T) {

for _, key := range []string{
propCanMaxHistory,
propScheduleWorkflowType,
propScheduleWorkflowName,
propScheduleID,
propScheduleInput,
} {
Expand All @@ -167,8 +167,7 @@ func TestDocumentMetadataDefinitionShape(t *testing.T) {
}

assert.Equal(t, typeInteger, def.Properties[propCanMaxHistory].Type)
assert.Equal(t, typeString, def.Properties[propScheduleWorkflowType].Type)
assert.False(t, def.Properties[propScheduleWorkflowType].Deprecated)
assert.Equal(t, typeString, def.Properties[propScheduleWorkflowName].Type)
assert.Equal(t, typeString, def.Properties[propScheduleID].Type)
assert.Equal(t, typeArray, def.Properties[propScheduleInput].Type)
}
Expand Down Expand Up @@ -225,8 +224,8 @@ func TestDocumentMetadata_Validation(t *testing.T) {
}{
// ---- valid cases ----
{
name: "scheduleWorkflowType string is accepted",
meta: map[string]any{propScheduleWorkflowType: testWorkflowType},
name: "scheduleWorkflowName string is accepted",
meta: map[string]any{"scheduleWorkflowName": "my-workflow"},
},
{
name: "scheduleId string is accepted",
Expand Down Expand Up @@ -263,13 +262,8 @@ func TestDocumentMetadata_Validation(t *testing.T) {

// ---- invalid cases ----
{
name: "scheduleWorkflowType as integer is rejected",
meta: map[string]any{propScheduleWorkflowType: 42},
expectError: true,
},
{
name: "empty scheduleWorkflowType is rejected",
meta: map[string]any{propScheduleWorkflowType: ""},
name: "scheduleWorkflowName as integer is rejected",
meta: map[string]any{"scheduleWorkflowName": 42},
expectError: true,
},
{
Expand Down
16 changes: 16 additions & 0 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ func BuildSchema(version, format string) (*jsonschema.Schema, error) {
Required: []string{propDo, propDocument},
Properties: schemaProperties,
Defs: buildDefinitions(),
If: &jsonschema.Schema{
Required: []string{"schedule"},
},
Then: &jsonschema.Schema{
Required: []string{propDocument},
Properties: map[string]*jsonschema.Schema{
propDocument: {
Required: []string{propMetadata},
Properties: map[string]*jsonschema.Schema{
propMetadata: {
Required: []string{propScheduleWorkflowName},
},
},
},
},
},
}

// Validate the generated schema
Expand Down
32 changes: 28 additions & 4 deletions schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -517,10 +517,10 @@
"title": "ScheduleInput",
"description": "Set the input."
},
"scheduleWorkflowType": {
"scheduleWorkflowName": {
"type": "string",
"title": "ScheduleWorkflowType",
"description": "The workflow type the schedule starts: the document.workflowType or a top-level Do task. Defaults to document.workflowType; required when every top-level task is a Do task.",
"title": "ScheduleWorkflowName",
"description": "Set the workflow name to trigger - this will either be the document.workflowType or the Do task",
"minLength": 1
}
},
Expand Down Expand Up @@ -1834,5 +1834,29 @@
"required": [
"do",
"document"
]
],
"if": {
"required": [
"schedule"
]
},
"then": {
"properties": {
"document": {
"properties": {
"metadata": {
"required": [
"scheduleWorkflowName"
]
}
},
"required": [
"metadata"
]
}
},
"required": [
"document"
]
}
}
23 changes: 18 additions & 5 deletions schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -305,12 +305,11 @@ $defs:
description: Set the input.
title: ScheduleInput
type: array
scheduleWorkflowType:
description: 'The workflow type the schedule starts: the document.workflowType
or a top-level Do task. Defaults to document.workflowType; required when
every top-level task is a Do task.'
scheduleWorkflowName:
description: Set the workflow name to trigger - this will either be the document.workflowType
or the Do task
minLength: 1
title: ScheduleWorkflowType
title: ScheduleWorkflowName
type: string
title: DocumentMetadata
type: object
Expand Down Expand Up @@ -1246,6 +1245,9 @@ $defs:
$id: https://zigflow.dev/schema.yaml
$schema: https://json-schema.org/draft/2020-12/schema
description: JSON Schema for a Zigflow workflow definition.
if:
required:
- schedule
properties:
do:
$ref: '#/$defs/taskList'
Expand Down Expand Up @@ -1337,5 +1339,16 @@ properties:
required:
- do
- document
then:
properties:
document:
properties:
metadata:
required:
- scheduleWorkflowName
required:
- metadata
required:
- document
title: Zigflow
type: object
58 changes: 34 additions & 24 deletions schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@ import (
"github.com/stretchr/testify/require"
)

const (
testCronExpr = "0 0 * * *"
testWorkflowType = "my-workflow"
)
const testCronExpr = "0 0 * * *"

// minimalWorkflow returns a minimal structurally valid workflow document.
// It is used as a baseline for validation tests so that each test only
Expand Down Expand Up @@ -100,28 +97,41 @@ func TestSchema_DocumentFields(t *testing.T) {
})
}

// TestSchema_ScheduleNeedsNoWorkflowKey verifies that a schedule validates
// without a workflow key: the runtime defaults it to document.workflowType, and
// only it can tell when that does not apply (every top-level task a Do task).
func TestSchema_ScheduleNeedsNoWorkflowKey(t *testing.T) {
// TestSchema_ScheduleRequiresScheduleWorkflowName verifies the conditional
// if/then rule: when top-level schedule is present, document.metadata must
// exist and contain scheduleWorkflowName.
func TestSchema_ScheduleRequiresScheduleWorkflowName(t *testing.T) {
resolved := resolvedTestSchema(t)

for name, meta := range map[string]map[string]any{
"no document.metadata": nil,
"metadata without a key": {propScheduleID: "my-schedule"},
"the workflow type key": {propScheduleWorkflowType: testWorkflowType},
"the old key, now unlisted": {"scheduleWorkflowName": testWorkflowType},
} {
t.Run(name, func(t *testing.T) {
doc := minimalWorkflow()
doc["schedule"] = map[string]any{propCron: testCronExpr}
if meta != nil {
doc["document"].(map[string]any)["metadata"] = meta
}

assert.NoError(t, resolved.Validate(doc), "a schedule must validate with %s", name)
})
}
t.Run("schedule present without document.metadata is rejected", func(t *testing.T) {
doc := minimalWorkflow()
doc["schedule"] = map[string]any{propCron: testCronExpr}

err := resolved.Validate(doc)
assert.Error(t, err, "schedule without document.metadata must fail validation")
})

t.Run("schedule present with document.metadata but missing scheduleWorkflowName is rejected", func(t *testing.T) {
doc := minimalWorkflow()
doc["schedule"] = map[string]any{propCron: testCronExpr}
doc["document"].(map[string]any)["metadata"] = map[string]any{
propScheduleID: "my-schedule",
}

err := resolved.Validate(doc)
assert.Error(t, err, "schedule with document.metadata but no scheduleWorkflowName must fail validation")
})

t.Run("schedule present with valid document.metadata.scheduleWorkflowName is accepted", func(t *testing.T) {
doc := minimalWorkflow()
doc["schedule"] = map[string]any{propCron: testCronExpr}
doc["document"].(map[string]any)["metadata"] = map[string]any{
propScheduleWorkflowName: "my-workflow",
}

err := resolved.Validate(doc)
assert.NoError(t, err, "schedule with valid scheduleWorkflowName must pass validation")
})
}

// TestSchema_RejectsUnknownTopLevelProperties verifies that the root schema
Expand Down
Loading