diff --git a/constants.go b/constants.go index 5c46403..92c1046 100644 --- a/constants.go +++ b/constants.go @@ -111,7 +111,7 @@ const ( propRetryPolicy = "retryPolicy" propTaskQueue = "taskQueue" propType = "type" - propScheduleWorkflowType = "scheduleWorkflowType" + propScheduleWorkflowName = "scheduleWorkflowName" propScheduleID = "scheduleId" propScheduleInput = "scheduleInput" propSeconds = "seconds" diff --git a/definitions.go b/definitions.go index f7ead7a..e4326ad 100644 --- a/definitions.go +++ b/definitions.go @@ -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, diff --git a/metadata_test.go b/metadata_test.go index f9b6622..6b177c4 100644 --- a/metadata_test.go +++ b/metadata_test.go @@ -158,7 +158,7 @@ func TestDocumentMetadataDefinitionShape(t *testing.T) { for _, key := range []string{ propCanMaxHistory, - propScheduleWorkflowType, + propScheduleWorkflowName, propScheduleID, propScheduleInput, } { @@ -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) } @@ -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", @@ -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, }, { diff --git a/schema.go b/schema.go index e093ee4..f2bc917 100644 --- a/schema.go +++ b/schema.go @@ -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 diff --git a/schema.json b/schema.json index e33add0..8bc006c 100644 --- a/schema.json +++ b/schema.json @@ -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 } }, @@ -1834,5 +1834,29 @@ "required": [ "do", "document" - ] + ], + "if": { + "required": [ + "schedule" + ] + }, + "then": { + "properties": { + "document": { + "properties": { + "metadata": { + "required": [ + "scheduleWorkflowName" + ] + } + }, + "required": [ + "metadata" + ] + } + }, + "required": [ + "document" + ] + } } diff --git a/schema.yaml b/schema.yaml index ed93248..15944dc 100644 --- a/schema.yaml +++ b/schema.yaml @@ -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 @@ -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' @@ -1337,5 +1339,16 @@ properties: required: - do - document +then: + properties: + document: + properties: + metadata: + required: + - scheduleWorkflowName + required: + - metadata + required: + - document title: Zigflow type: object diff --git a/schema_test.go b/schema_test.go index 986249c..6d01839 100644 --- a/schema_test.go +++ b/schema_test.go @@ -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 @@ -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