diff --git a/internal/model/load.go b/internal/model/load.go index 97d3a7f..4f46e49 100644 --- a/internal/model/load.go +++ b/internal/model/load.go @@ -16,6 +16,9 @@ func LoadCluster(path string) (Cluster, error) { if err := loadYAML(path, "cluster", &cluster); err != nil { return Cluster{}, err } + if cluster.Nodes == nil { + return Cluster{}, fmt.Errorf("validate cluster file %q: top-level field %q is required and must be a sequence", path, "nodes") + } if err := ValidateCluster(cluster); err != nil { return Cluster{}, fmt.Errorf("validate cluster file %q: %w", path, err) } @@ -28,6 +31,9 @@ func LoadJobs(path string) (JobSet, error) { if err := loadYAML(path, "jobs", &jobs); err != nil { return JobSet{}, err } + if jobs.Jobs == nil { + return JobSet{}, fmt.Errorf("validate jobs file %q: top-level field %q is required and must be a sequence", path, "jobs") + } if err := ValidateJobs(jobs); err != nil { return JobSet{}, fmt.Errorf("validate jobs file %q: %w", path, err) } diff --git a/internal/model/load_test.go b/internal/model/load_test.go index 662c57f..26e84ee 100644 --- a/internal/model/load_test.go +++ b/internal/model/load_test.go @@ -34,6 +34,26 @@ func TestLoadCluster(t *testing.T) { Capacity: model.Resources{CPU: 32, GPU: 4}, }}}, }, + { + name: "explicit empty sequence", + contents: "nodes: []\n", + want: model.Cluster{Nodes: []model.Node{}}, + }, + { + name: "missing nodes field", + contents: "{}\n", + wantErrText: `top-level field "nodes" is required and must be a sequence`, + }, + { + name: "null document", + contents: "null\n", + wantErrText: `top-level field "nodes" is required and must be a sequence`, + }, + { + name: "null nodes field", + contents: "nodes: null\n", + wantErrText: `top-level field "nodes" is required and must be a sequence`, + }, { name: "unknown root field", contents: `nodes: [] @@ -135,6 +155,26 @@ func TestLoadJobs(t *testing.T) { {Name: "second", Replicas: 1, ResourcesPerReplica: model.Resources{GPU: 1}, RequiredTopology: "rack"}, }}, }, + { + name: "explicit empty sequence", + contents: "jobs: []\n", + want: model.JobSet{Jobs: []model.Job{}}, + }, + { + name: "missing jobs field", + contents: "{}\n", + wantErrText: `top-level field "jobs" is required and must be a sequence`, + }, + { + name: "null document", + contents: "null\n", + wantErrText: `top-level field "jobs" is required and must be a sequence`, + }, + { + name: "null jobs field", + contents: "jobs: null\n", + wantErrText: `top-level field "jobs" is required and must be a sequence`, + }, { name: "unknown job field", contents: `jobs: