diff --git a/internal/framework/runner/runner.go b/internal/framework/runner/runner.go index 3f1412a..1555d7a 100644 --- a/internal/framework/runner/runner.go +++ b/internal/framework/runner/runner.go @@ -56,6 +56,9 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) { if err != nil { return output, fmt.Errorf("build extractor %q: %w", extractorKey, err) } + if extractor == nil { + return output, fmt.Errorf("build extractor %q: returned nil extractor", extractorKey) + } result, err := extractor.Extract(ctx, contracts.ExtractionRequest{ Source: input.Source, @@ -121,7 +124,10 @@ func runValidators(ctx context.Context, extractor contracts.Extractor, doc *sour var rejected []artifacts.RejectedArtifact var warnings []contracts.Warning - for _, validator := range extractor.Validators() { + for validatorIndex, validator := range extractor.Validators() { + if validator == nil { + return nil, rejected, warnings, fmt.Errorf("extractor %q validator[%d] must not be nil", extractor.Key(), validatorIndex) + } result, err := validator.Validate(ctx, contracts.ValidationRequest{ Source: doc, Candidates: eligible, diff --git a/internal/framework/runner/runner_test.go b/internal/framework/runner/runner_test.go index 4ea009f..4c734fa 100644 --- a/internal/framework/runner/runner_test.go +++ b/internal/framework/runner/runner_test.go @@ -83,6 +83,19 @@ func TestRunRejectsInvalidSetup(t *testing.T) { } } +func TestRunRejectsNilExtractorFromFactory(t *testing.T) { + factory := fakeFactory{extractors: map[string]contracts.Extractor{ + "generic-extractor": nil, + }} + + _, err := New(factory).Run(context.Background(), RunInput{ + Source: validSourceDocument(), + ExtractorKeys: []string{"generic-extractor"}, + }) + + assertRunError(t, err, "returned nil extractor") +} + func TestRunUsesConfiguredExtractorOrderAndAssignsGlobalIndices(t *testing.T) { var order []string var seenIndices []int @@ -251,6 +264,25 @@ func TestRunSurfacesValidatorCardinalityError(t *testing.T) { assertRunError(t, err, "0 decisions for 1 candidates") } +func TestRunRejectsNilValidator(t *testing.T) { + factory := fakeFactory{extractors: map[string]contracts.Extractor{ + "generic-extractor": fakeExtractor{ + key: "generic-extractor", + artifactType: "generic-artifact", + schemaVersion: "v1", + candidateCount: 1, + validators: []contracts.Validator{nil}, + }, + }} + + _, err := New(factory).Run(context.Background(), RunInput{ + Source: validSourceDocument(), + ExtractorKeys: []string{"generic-extractor"}, + }) + + assertRunError(t, err, "validator[0] must not be nil") +} + func TestRunCollectsExtractorAndValidatorWarnings(t *testing.T) { validator := fakeValidator{ name: "generic-validator",