Skip to main content

cmd/shadowdiff/main.go

Source

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 to shadowdiff.config.json).
  • Load configuration and fixtures via internal/shadowdiff.
  • Construct a shadowdiff.Runner with 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

cmd/shadowdiff/main.go#L3
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.

cmd/shadowdiff/main.go#L13
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.
  • 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.
  • 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 ... range loop.
    • 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.
      • 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.
  • 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.

Guides

Neighboring source