Add new step type: step.static_file for serving files from disk#250
Add new step type: step.static_file for serving files from disk#250
Conversation
|
@copilot retry |
…disk Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Implemented The new step type:
Example usage: - name: serve_spec
type: step.static_file
config:
file: ./specs/chimera-api.yaml
content_type: "application/yaml"
cache_control: "public, max-age=3600" |
There was a problem hiding this comment.
Pull request overview
Adds a new built-in pipeline step type, step.static_file, to serve a single file from disk as an HTTP response (with init-time file loading and config-relative path resolution), intended as a lightweight alternative to static.fileserver for simple one-file routes.
Changes:
- Implement
step.static_filestep factory + execution behavior (HTTP-writer mode + no-writer fallback). - Register the new step type across plugin manifests/factories, schema registries, and wfctl type registry.
- Add unit tests and update plugin step factory expectations.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
module/pipeline_step_static_file.go |
Implements the new step.static_file step (init-time file read; writes HTTP response or returns body in output when no writer). |
module/pipeline_step_static_file_test.go |
Adds unit tests for serving content, no-writer output mode, and config validation/error cases. |
plugins/pipelinesteps/plugin.go |
Registers step.static_file in StepTypes and StepFactories. |
plugins/pipelinesteps/plugin_test.go |
Updates expectedSteps list to include step.static_file. |
schema/schema.go |
Adds step.static_file to coreModuleTypes. |
schema/module_schema.go |
Registers the module schema for step.static_file (config fields + description). |
cmd/wfctl/type_registry.go |
Adds step.static_file to wfctl known step types/config keys. |
| func TestStaticFileStep_MissingContentType(t *testing.T) { | ||
| dir := t.TempDir() | ||
| filePath := filepath.Join(dir, "data.txt") | ||
| _ = os.WriteFile(filePath, []byte("hello"), 0o600) |
There was a problem hiding this comment.
os.WriteFile errors are ignored here. If the write fails, this test may pass/fail for the wrong reason (it will later error on missing content_type rather than setup failure). Please assert the write succeeded (like the other tests in this file).
| _ = os.WriteFile(filePath, []byte("hello"), 0o600) | |
| if err := os.WriteFile(filePath, []byte("hello"), 0o600); err != nil { | |
| t.Fatalf("write temp file: %v", err) | |
| } |
schema/module_schema.go
Outdated
| Category: "pipeline", | ||
| Description: "Serves a static file from disk as an HTTP response; file is read at init time for performance", | ||
| Inputs: []ServiceIODef{{Name: "context", Type: "PipelineContext", Description: "Pipeline context (HTTP response writer)"}}, | ||
| Outputs: []ServiceIODef{{Name: "result", Type: "StepResult", Description: "HTTP response with file content"}}, |
There was a problem hiding this comment.
The schema output description is a bit misleading/inconsistent with other response steps: when an HTTP writer is present, the file content is written to the response but is not present in StepResult.Output. Consider wording this like step.json_response (e.g., “writes an HTTP response and stops the pipeline”; optionally mention the no-writer fallback returns body).
| Outputs: []ServiceIODef{{Name: "result", Type: "StepResult", Description: "HTTP response with file content"}}, | |
| Outputs: []ServiceIODef{{Name: "result", Type: "StepResult", Description: "Writes an HTTP response with file content and stops the pipeline; if no HTTP writer is available, returns the file content in StepResult.Output as body"}}, |
| factory := NewStaticFileStepFactory() | ||
| step, err := factory("serve_spec", map[string]any{ | ||
| "file": filePath, | ||
| "content_type": "application/yaml", | ||
| "cache_control": "public, max-age=3600", | ||
| }, nil) | ||
| if err != nil { | ||
| t.Fatalf("factory error: %v", err) |
There was a problem hiding this comment.
The tests don’t cover the advertised config-relative path resolution via _config_dir/config.ResolvePathInConfig (i.e., passing a relative file path and setting _config_dir in the step config). Adding a test for this would guard the primary feature this step is introducing.
| resp := recorder.Result() | ||
| if resp.StatusCode != 200 { | ||
| t.Errorf("expected status 200, got %d", resp.StatusCode) | ||
| } | ||
| if ct := resp.Header.Get("Content-Type"); ct != "application/yaml" { | ||
| t.Errorf("expected Content-Type application/yaml, got %q", ct) | ||
| } | ||
| if cc := resp.Header.Get("Cache-Control"); cc != "public, max-age=3600" { | ||
| t.Errorf("expected Cache-Control header, got %q", cc) | ||
| } | ||
|
|
||
| body, _ := io.ReadAll(resp.Body) | ||
| if string(body) != content { | ||
| t.Errorf("expected body %q, got %q", content, string(body)) |
There was a problem hiding this comment.
Avoid ignoring I/O errors in tests: io.ReadAll(resp.Body) drops the returned error, and resp.Body isn’t closed. Checking the read error and closing the body will prevent false positives and resource leaks if the response body can’t be read for some reason.
|
@copilot apply changes based on the comments in this thread |
Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Applied all 4 review changes in commit c159f2b:
|
module/pipeline_step_static_file.goimplementationmodule/pipeline_step_static_file_test.goos.WriteFileerror inTestStaticFileStep_MissingContentTypeio.ReadAllerror and missingresp.Body.Close()inTestStaticFileStep_ServesFileTestStaticFileStep_ConfigRelativePathto cover_config_dir-relative path resolutionplugins/pipelinesteps/plugin.go(StepTypes + StepFactories)schema/schema.gocoreModuleTypesschema/module_schema.gocmd/wfctl/type_registry.goplugins/pipelinesteps/plugin_test.goexpectedSteps listOriginal prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.