cmd/shadowdiff/main.go
Source
- Package:
main - File:
cmd/shadowdiff/main.go - GitHub: https://github.com/theroutercompany/api_router/blob/main/cmd/shadowdiff/main.go
Overview
What:
CLI entrypoint for running a shadow diff session.
It replays captured HTTP fixtures against two gateway implementations:
- a Node-based gateway
- the Go gateway in this repository
It then reports any mismatches in status code and (normalized) JSON bodies.
Why:
Shadow diff is the migration safety net when porting gateway behavior from Node to Go.
Instead of relying on manual spot checks, shadow diff:
- runs a repeatable set of fixture requests
- measures latency for each implementation
- normalizes away volatile response fields (timestamps, uptime, etc)
- prints diffs in a human-readable form
How:
- Parse
--config(defaults toshadowdiff.config.json). - Load configuration and fixtures via
internal/shadowdiff. - Construct a
shadowdiff.Runnerwith JSON normalizers. - Run the fixture set within a timeout context.
- Print per-fixture mismatches and a summary count.
Notes:
Prefer using scripts/shadowdiff-run.sh for local runs because it also boots mock upstreams
and starts a Go gateway instance wired to them.
Contents
Imports
import block 1
import (
"context"
"flag"
"fmt"
"log"
"time"
"github.com/theroutercompany/api_router/internal/shadowdiff"
)
Functions and Methods
main
What: Runs the shadowdiff CLI flow end-to-end.
Why: Provides a simple executable wrapper around internal/shadowdiff for humans and scripts.
How: Loads config and fixtures, configures a runner (including normalizers), executes the run with a timeout, prints any diffs, and emits a final summary.
func main() {
configPath := flag.String("config", "shadowdiff.config.json", "Path to shadow diff configuration")
flag.Parse()
cfg, err := shadowdiff.LoadConfig(*configPath)
if err != nil {
log.Fatalf("load config: %v", err)
}
fixtures, err := shadowdiff.LoadFixtures(cfg.Fixtures)
if err != nil {
log.Fatalf("load fixtures: %v", err)
}
runner := shadowdiff.Runner{
Config: cfg,
Normalizers: []func([]byte) []byte{
shadowdiff.StripJSONKeys("timestamp", "uptime", "checkedAt", "latencyMs"),
},
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
results := runner.Run(ctx, fixtures)
var diffCount int
for _, result := range results {
if result.Err != nil {
fmt.Printf("[%s] error: %v\n", result.Fixture.Name, result.Err)
continue
}
statusMatch := result.NodeStatus == result.GoStatus
bodyMatch := result.BodyDiff == ""
if !statusMatch || !bodyMatch {
diffCount++
fmt.Printf("[%s] status node=%d go=%d\n", result.Fixture.Name, result.NodeStatus, result.GoStatus)
if result.BodyDiff != "" {
fmt.Println(result.BodyDiff)
}
}
}
fmt.Printf("Processed %d fixtures, %d diffs found\n", len(results), diffCount)
}
Walkthrough
The list below documents the statements inside the function body, including nested blocks and inline closures.
- L14:
configPath := flag.String("config", "shadowdiff.config.json", "Path to shadow diff configuration")- What: Defines configPath.
- 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.
- L15:
flag.Parse()- What: Calls flag.Parse.
- Why: Performs side effects or delegates work to a helper.
- How: Executes the expression statement.
- L17:
cfg, err := shadowdiff.LoadConfig(*configPath)- What: Defines cfg, 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.
- L18:
if err != nil { log.Fatalf("load config: %v", err) }- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L19:
log.Fatalf("load config: %v", err)- What: Calls log.Fatalf.
- Why: Performs side effects or delegates work to a helper.
- How: Executes the expression statement.
- L19:
- L22:
fixtures, err := shadowdiff.LoadFixtures(cfg.Fixtures)- What: Defines fixtures, 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.
- L23:
if err != nil { log.Fatalf("load fixtures: %v", err) }- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L24:
log.Fatalf("load fixtures: %v", err)- What: Calls log.Fatalf.
- Why: Performs side effects or delegates work to a helper.
- How: Executes the expression statement.
- L24:
- L27:
runner := shadowdiff.Runner{ Config: cfg, Normalizers: []func([]byte) []byte{ shadowdiff.StripJSONKeys("timestamp", "uptime", "checkedAt", …- What: Defines runner.
- 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.
- L33:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)- What: Defines ctx, cancel.
- 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:
defer cancel()- What: Defers a call for cleanup.
- Why: Ensures the deferred action runs even on early returns.
- How: Schedules the call to run when the surrounding function returns.
- L36:
results := runner.Run(ctx, fixtures)- What: Defines results.
- 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.
- L38:
var diffCount int- What: Declares local names.
- Why: Introduces variables or types used later in the function.
- How: Executes a Go declaration statement inside the function body.
- L39:
for _, result := range results { if result.Err != nil { fmt.Printf("[%s] error: %v\n", result.Fixture.Name, result.Err) continue } statusMa…- What: Iterates over a collection.
- Why: Processes multiple elements with the same logic.
- How: Executes a
for ... rangeloop. - Nested steps:
- L40:
if result.Err != nil { fmt.Printf("[%s] error: %v\n", result.Fixture.Name, result.Err) continue }- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L41:
fmt.Printf("[%s] error: %v\n", result.Fixture.Name, result.Err)- What: Calls fmt.Printf.
- Why: Performs side effects or delegates work to a helper.
- How: Executes the expression statement.
- L42:
continue- What: Executes a statement.
- Why: Advances the function logic.
- How: Runs this statement as part of the function body.
- L41:
- L45:
statusMatch := result.NodeStatus == result.GoStatus- What: Defines statusMatch.
- 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.
- L46:
bodyMatch := result.BodyDiff == ""- What: Defines bodyMatch.
- 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.
- L48:
if !statusMatch || !bodyMatch { diffCount++ fmt.Printf("[%s] status node=%d go=%d\n", result.Fixture.Name, result.NodeStatus, result.GoStat…- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L49:
diffCount++- What: Updates a counter.
- Why: Maintains an index or tally used by subsequent logic.
- How: Executes an increment/decrement statement.
- L50:
fmt.Printf("[%s] status node=%d go=%d\n", result.Fixture.Name, result.NodeStatus, result.GoStatus)- What: Calls fmt.Printf.
- Why: Performs side effects or delegates work to a helper.
- How: Executes the expression statement.
- L51:
if result.BodyDiff != "" { fmt.Println(result.BodyDiff) }- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L52:
fmt.Println(result.BodyDiff)- What: Calls fmt.Println.
- Why: Performs side effects or delegates work to a helper.
- How: Executes the expression statement.
- L52:
- L49:
- L40:
- L57:
fmt.Printf("Processed %d fixtures, %d diffs found\n", len(results), diffCount)- What: Calls fmt.Printf.
- Why: Performs side effects or delegates work to a helper.
- How: Executes the expression statement.