Skip to main content

internal/shadowdiff/fixture.go

Source

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

internal/shadowdiff/fixture.go#L3
import (
"encoding/json"
"fmt"
"os"
)

Types

type block 1

internal/shadowdiff/fixture.go#L10
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.

internal/shadowdiff/fixture.go#L20
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 ... range loop.
    • 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 return statement (possibly returning values).
      • 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 return statement (possibly returning values).
      • 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.
  • 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 return statement (possibly returning values).

Guides

Neighboring source