Ctrl K
ComunidadGuías…

pkg/models/postgres_v3_response_validate.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package models

import "errors"

// ErrPostgresV3ResponseInvalid is the sentinel returned by
// PostgresV3Response.Validate() when the response carries a
// schema-level mutual-exclusion violation. Callers that want to
// distinguish validation failures from unrelated load errors can
// errors.Is against this sentinel; the wrapped message identifies
// which axes collided.
var ErrPostgresV3ResponseInvalid = errors.New("invalid PostgresV3Response")

// Validate enforces the wire-protocol-level mutual-exclusion rules
// across PostgresV3Response's payload axes.
//
// A single Postgres backend response carries exactly ONE of:
//
//   - DataRow traffic (Rows + RowDescription + CommandComplete)
//   - CopyOut traffic (CopyOutResponse + CopyData… + CopyDone)
//   - CopyIn handshake (CopyInResponse — client streams data back)
//   - FunctionCallResponse (legacy fastpath FunctionCall)
//
// The struct exposes each axis as an independent field for
// serialisation convenience, but the ONLY valid populated-field
// combinations on a single response are exactly one axis (plus
// any number of orthogonal Notices / Notifications / an Error).
//
// Without this enforcement, a malformed mock with two axes set
// would (a) serialise cleanly, (b) deserialise cleanly, (c) be
// loaded by the replay path, and (d) cause the wire emitter to
// nondeterministically pick one axis and silently drop the other —
// a class of "missing rows" / "missing CopyOut" replay bug that's
// hard to diagnose because no error surfaces. Validate() is the
// load-time guard against that.
//
// Note that Error is intentionally NOT considered a payload axis:
// a backend ErrorResponse can legitimately ride alongside a partial
// Rows burst (the server may have streamed some rows before raising
// the error). Notices and Notifications are similarly orthogonal —
// they interleave with any payload axis on the wire.
func (r *PostgresV3Response) Validate() error {
	if r == nil {
		return nil
	}

	hasRows := len(r.Rows) > 0
	hasCopyOut := r.CopyOut != nil
	hasCopyIn := r.CopyIn != nil
	hasFnCall := r.FunctionCall != nil

	// Pairwise mutual-exclusion checks. We report each violating
	// pair separately so a multiply-malformed response surfaces
	// every collision in one error rather than playing whack-a-mole
	// across reload cycles.
	var collisions []string
	if hasRows && hasCopyOut {
		collisions = append(collisions, "Rows and CopyOut populated together")
	}
	if hasRows && hasCopyIn {
		collisions = append(collisions, "Rows and CopyIn populated together")
	}
	if hasCopyOut && hasCopyIn {
		collisions = append(collisions, "CopyOut and CopyIn populated together")
	}
	if hasFnCall && hasRows {
		collisions = append(collisions, "FunctionCall and Rows populated together")
	}
	if hasFnCall && hasCopyOut {
		collisions = append(collisions, "FunctionCall and CopyOut populated together")
	}
	if hasFnCall && hasCopyIn {
		collisions = append(collisions, "FunctionCall and CopyIn populated together")
	}

	if len(collisions) == 0 {
		return nil
	}

	msg := "PostgresV3Response payload axes are mutually exclusive but found: "
	for i, c := range collisions {
		if i > 0 {
			msg += "; "
		}
		msg += c
	}
	return errors.Join(ErrPostgresV3ResponseInvalid, errors.New(msg))
}