internal/shadowdiff/fixture.go
Source
- Package:
shadowdiff - File:
internal/shadowdiff/fixture.go - GitHub: https://github.com/theroutercompany/api_router/blob/main/internal/shadowdiff/fixture.go
Overview
What:
Defines the fixture schema used by shadowdiff and provides a helper to load fixture files from disk.
A fixture is a captured HTTP request description plus minimal expectations used for analysis.
Why:
Shadow diff runs should be deterministic and easy to share.
Storing fixtures as JSON files on disk makes it easy to:
- add new scenarios by committing new fixture files
- review and version fixture changes like code
- replay the same requests against different gateway versions
How:
LoadFixtures(paths) reads each fixture file (each file contains a JSON array of fixtures),
decodes it into []Fixture, and concatenates the results into one slice.
Notes:
Fixtures are request-focused. The runner currently compares response status and body,
and uses ExpectStatus mostly as metadata for humans.
Contents
Imports
import block 1
import (
"encoding/json"
"fmt"
"os"
)
Types
type block 1
type Fixture struct {
Name string `json:"name"`
Method string `json:"method"`
Path string `json:"path"`
Headers map[string]string `json:"headers"`
Body json.RawMessage `json:"body"`
ExpectStatus int `json:"expectStatus"`
}
Fixture
What: A single captured HTTP request scenario.
Why: Provides the input needed to replay a request (method, path, headers, body) and label it for reporting.
How: Stored and loaded as JSON; the runner uses it to build requests and to label output.
Notes: The Body field uses json.RawMessage so fixtures can represent arbitrary JSON payloads without schema coupling.
Functions and Methods
LoadFixtures
What: Loads one or more fixture files from disk.
Why: Allows a shadowdiff session to be composed from multiple fixture files (grouped by feature or endpoint).
How: For each path, reads the file, unmarshals an array of fixtures, and appends them into a single slice.
Notes: Errors include the fixture path for debugging.
func LoadFixtures(paths []string) ([]Fixture, error) {
var fixtures []Fixture
for _, path := range paths {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read fixture %s: %w", path, err)
}
var fileFixtures []Fixture
if err := json.Unmarshal(data, &fileFixtures); err != nil {
return nil, fmt.Errorf("decode fixture %s: %w", path, err)
}
fixtures = append(fixtures, fileFixtures...)
}
return fixtures, nil
}
Walkthrough
The list below documents the statements inside the function body, including nested blocks and inline closures.
- L21:
var fixtures []Fixture- What: Declares local names.
- Why: Introduces variables or types used later in the function.
- How: Executes a Go declaration statement inside the function body.
- L22:
for _, path := range paths { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read fixture %s: %w", path, err) } var …- What: Iterates over a collection.
- Why: Processes multiple elements with the same logic.
- How: Executes a
for ... rangeloop. - Nested steps:
- L23:
data, err := os.ReadFile(path)- What: Defines data, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L24:
if err != nil { return nil, fmt.Errorf("read fixture %s: %w", path, err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L25:
return nil, fmt.Errorf("read fixture %s: %w", path, err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L25:
- L28:
var fileFixtures []Fixture- What: Declares local names.
- Why: Introduces variables or types used later in the function.
- How: Executes a Go declaration statement inside the function body.
- L29:
if err := json.Unmarshal(data, &fileFixtures); err != nil { return nil, fmt.Errorf("decode fixture %s: %w", path, err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L29:
err := json.Unmarshal(data, &fileFixtures)- What: Defines err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L30:
return nil, fmt.Errorf("decode fixture %s: %w", path, err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L29:
- L32:
fixtures = append(fixtures, fileFixtures...)- What: Assigns fixtures.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L23:
- L34:
return fixtures, nil- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).