doctor API

doctor

package

API reference for the doctor package.

T
type

Severity

app/doctor/doctor.go:12-12
type Severity string
S
struct

Check

app/doctor/doctor.go:20-24
type Check struct

Fields

Name Type Description
Severity Severity json:"severity"
Name string json:"name"
Message string json:"message"
S
struct

Route

app/doctor/doctor.go:26-29
type Route struct

Fields

Name Type Description
Method string json:"method"
Path string json:"path"
S
struct

Source

app/doctor/doctor.go:31-33
type Source struct

Fields

Name Type Description
Routes []Route json:"routes"
S
struct

Report

app/doctor/doctor.go:35-37
type Report struct

Methods

Failures
Method

Returns

int
func (Report) Failures() int
{
	n := 0
	for _, check := range r.Checks {
		if check.Severity == Fail {
			n++
		}
	}
	return n
}
Warnings
Method

Returns

int
func (Report) Warnings() int
{
	n := 0
	for _, check := range r.Checks {
		if check.Severity == Warn {
			n++
		}
	}
	return n
}
WriteText
Method

Parameters

Returns

error
func (Report) WriteText(w io.Writer) error
{
	if _, err := fmt.Fprintln(w, "foundation doctor"); err != nil {
		return err
	}
	if _, err := fmt.Fprintln(w); err != nil {
		return err
	}
	for _, check := range r.Checks {
		if _, err := fmt.Fprintf(w, "%s %s: %s\n", check.Severity, check.Name, check.Message); err != nil {
			return err
		}
	}
	return nil
}
WriteJSON
Method

Parameters

Returns

error
func (Report) WriteJSON(w io.Writer) error
{
	enc := json.NewEncoder(w)
	enc.SetIndent("", "  ")
	return enc.Encode(r)
}

Fields

Name Type Description
Checks []Check json:"checks"
F
function

CheckSource

Parameters

src

Returns

app/doctor/doctor.go:39-46
func CheckSource(src Source) Report

{
	var checks []Check

	checks = append(checks, checkRoutes(src.Routes)...)
	checks = append(checks, checkHealth(src.Routes))

	return Report{Checks: checks}
}
F
function

Run

Parameters

src

Returns

error
app/doctor/doctor.go:89-91
func Run(src Source) error

{
	return RunWithEnv(src, os.Getenv("FOUNDATION_DOCTOR"), os.Stdout)
}
F
function

RunWithEnv

Parameters

src
mode
string

Returns

error
app/doctor/doctor.go:93-116
func RunWithEnv(src Source, mode string, w io.Writer) error

{
	report := CheckSource(src)
	mode = strings.ToLower(strings.TrimSpace(mode))
	if mode == "" {
		mode = "print"
	}

	switch mode {
	case "off":
		return nil
	case "json":
		return report.WriteJSON(w)
	case "fail":
		if err := report.WriteText(w); err != nil {
			return err
		}
		if failures := report.Failures(); failures > 0 {
			return fmt.Errorf("foundation doctor: %d failure(s)", failures)
		}
		return nil
	default:
		return report.WriteText(w)
	}
}
F
function

checkRoutes

Parameters

routes

Returns

app/doctor/doctor.go:118-145
func checkRoutes(routes []Route) []Check

{
	if len(routes) == 0 {
		return []Check{{
			Severity: Fail,
			Name:     "routes",
			Message:  "no routes registered",
		}}
	}

	seen := make(map[string]bool, len(routes))
	for _, route := range routes {
		key := route.Method + " " + route.Path
		if seen[key] {
			return []Check{{
				Severity: Fail,
				Name:     "routes",
				Message:  "duplicate route " + key,
			}}
		}
		seen[key] = true
	}

	return []Check{{
		Severity: OK,
		Name:     "routes",
		Message:  fmt.Sprintf("%d route(s) registered", len(routes)),
	}}
}
F
function

checkHealth

Parameters

routes

Returns

app/doctor/doctor.go:147-174
func checkHealth(routes []Route) Check

{
	paths := make([]string, 0, len(routes))
	for _, route := range routes {
		if route.Method == "GET" {
			paths = append(paths, route.Path)
		}
	}
	sort.Strings(paths)

	hasLive := false
	hasReady := false
	for _, path := range paths {
		if path == "/health" || path == "/health/live" {
			hasLive = true
		}
		if path == "/health/ready" {
			hasReady = true
		}
	}

	if hasLive && hasReady {
		return Check{Severity: OK, Name: "health", Message: "liveness and readiness routes registered"}
	}
	if hasLive {
		return Check{Severity: Warn, Name: "health", Message: "liveness route registered without readiness route"}
	}
	return Check{Severity: Warn, Name: "health", Message: "no health route registered"}
}
F
function

TestCheckSourceRoutesAndHealth

Parameters

app/doctor/doctor_test.go:9-21
func TestCheckSourceRoutesAndHealth(t *testing.T)

{
	report := CheckSource(Source{Routes: []Route{
		{Method: "GET", Path: "/health/live"},
		{Method: "GET", Path: "/health/ready"},
	}})

	if report.Failures() != 0 {
		t.Fatalf("Failures() = %d, want 0", report.Failures())
	}
	if report.Warnings() != 0 {
		t.Fatalf("Warnings() = %d, want 0", report.Warnings())
	}
}
F
function

TestCheckSourceFailsWithoutRoutes

Parameters

app/doctor/doctor_test.go:23-29
func TestCheckSourceFailsWithoutRoutes(t *testing.T)

{
	report := CheckSource(Source{})

	if report.Failures() != 1 {
		t.Fatalf("Failures() = %d, want 1", report.Failures())
	}
}
F
function

TestRunWithEnvPrintsText

Parameters

app/doctor/doctor_test.go:31-41
func TestRunWithEnvPrintsText(t *testing.T)

{
	var buf bytes.Buffer

	err := RunWithEnv(Source{Routes: []Route{{Method: "GET", Path: "/ping"}}}, "print", &buf)
	if err != nil {
		t.Fatalf("RunWithEnv() error = %v", err)
	}
	if !strings.Contains(buf.String(), "foundation doctor") {
		t.Fatalf("output = %q", buf.String())
	}
}
F
function

TestRunWithEnvFailsOnFailures

Parameters

app/doctor/doctor_test.go:43-50
func TestRunWithEnvFailsOnFailures(t *testing.T)

{
	var buf bytes.Buffer

	err := RunWithEnv(Source{}, "fail", &buf)
	if err == nil {
		t.Fatal("RunWithEnv() error = nil, want error")
	}
}
F
function

TestRunWithEnvWritesJSON

Parameters

app/doctor/doctor_test.go:52-62
func TestRunWithEnvWritesJSON(t *testing.T)

{
	var buf bytes.Buffer

	err := RunWithEnv(Source{Routes: []Route{{Method: "GET", Path: "/ping"}}}, "json", &buf)
	if err != nil {
		t.Fatalf("RunWithEnv() error = %v", err)
	}
	if !strings.Contains(buf.String(), `"checks"`) {
		t.Fatalf("output = %q", buf.String())
	}
}