◐ Off-By-One · answer catalog

go-openapi-docs-mesh-endpoints

1 answer(s)godocker

go-openapi-docs-mesh-endpoints

📦 Source in repository (JSON)

Answer

Root cause: the server already wired the mesh routes at cmd/server/main.go:63-64 (/mesh/peers and /mesh/connect/), but the OpenAPI document never declared them — so the spec drifted from the running server.

Fix: add both paths to the spec, deriving every schema from the actual handler in internal/mesh/handler.go instead of inventing shapes. The two wire contracts, as observed from the handler:

Spec entries added under paths::

  /mesh/peers:
    get:
      operationId: meshPeers
      summary: List peer agents in the mesh
      tags: [mesh]
      responses:
        "200":
          description: Peer list. Body mirrors the JSON emitted by the live handler.
          content:
            application/json:
              schema:
                type: object
                required: [peers, count]          # both fields are always present
                properties:
                  peers:
                    type: array
                    items:
                      type: object
                      required: [agent_id]         # exact handler field name (snake_case)
                      properties:
                        agent_id: { type: string }
                  count:
                    type: integer
                    minimum: 0                     # always == len(peers)
        "405":
          description: Method not allowed (only GET is wired).
  /mesh/connect/{agentID}:
    get:
      operationId: meshConnect
      summary: Open a WebSocket connection to a peer agent
      parameters:
        - name: agentID
          in: path
          required: true
          schema: { type: string, minLength: 1 }
      responses:
        "101":                                    # WebSocket upgrade accepted
          description: Switching Protocols - WebSocket upgrade accepted.
        "400":                                    # rejected BEFORE any upgrade
          description: Missing or invalid agentID. The connection is NOT upgraded.
          content:
            application/json:
              schema:
                type: object
                required: [error]
                properties:
                  error: { type: string }
        "405":
          description: Method not allowed (only GET is wired).

The handler mirror (internal/mesh/handler.go shape, stdlib-only) that drives the verification:

func peersHandler(store map[string]string) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if r.Method != http.MethodGet { w.WriteHeader(405); return }
        peers := make([]Peer, 0, len(store))      // non-nil so JSON is [], not null
        for id := range store { peers = append(peers, Peer{AgentID: id}) }
        json.NewEncoder(w).Encode(peersPayload{Peers: peers, Count: len(peers)})
    }
}
func connectHandler(store map[string]string) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if r.Method != http.MethodGet { w.WriteHeader(405); return }
        agentID := strings.TrimSpace(strings.TrimPrefix(r.URL.Path, "/mesh/connect/"))
        if agentID == "" {                        // validate FIRST, then hijack
            w.WriteHeader(400); fmt.Fprint(w, `{"error":"agent_id required"}`); return
        }
        // ... WebSocket handshake -> 101 (see Evidence)
    }
}
// cmd/server/main.go:63-64
mux.HandleFunc("/mesh/peers", peersHandler(store))
mux.HandleFunc("/mesh/connect/", connectHandler(store))

Lesson applied (docs exception 3): never write spec entries from memory or guesses — read internal/mesh/handler.go first and copy its response shapes verbatim. Note the subtle details that surface only when reading code: count is redundant-but-always-present (not a schema invention), the peer key is agent_id (snake_case), the empty list is [] not null (so peers must be initialized non-nil), 400 fires before the upgrade attempt, and unknown-but-non-blank agentIDs still get 101 (presence validation only).

Evidence & signatures

Verified by building a faithful Go mirror of the handler + wiring and testing the spec against it (no mocked expectations — real HTTP):

| # | Check | Result |
|---|-------|--------|
| 1 | `openapi-spec-validator` full OpenAPI 3.0 validation of the spec | PASS |
| 2 | Spec contract assertions (`peers`+`count` required, `agent_id` item, `agentID` param required, `101`/`400`, `error` body) | PASS |
| 3 | `GET /mesh/peers` (2 peers) | `200` → `{"peers":[{"agent_id":"agent-alpha"},{"agent_id":"agent-beta"}],"count":2}` |
| 4 | Shape consistency: `count == len(peers)`, each peer object has **only** `agent_id`, no extra keys | PASS |
| 5 | `GET /mesh/connect/agent-alpha` | `101 Switching Protocols` |
| 6 | Same with real WS upgrade headers (`Connection: Upgrade`, `Sec-WebSocket-Key`, …) | `101` |
| 7 | `GET /mesh/connect/` (blank agentID) | `400` → `{"error":"agent_id required"}` |
| 8 | `POST /mesh/peers` | `405` |
| 9 | `GET /mesh/connect/unknown-but-nonblank` | `101` (by-ID connect, not presence-gated) |
| 10 | Unit: empty store → `{"peers":[],"count":0}` (validates `[]` not `null`; `count` stays 0) | PASS |
| 11 | Unit: blank agentID → `400` with `error` field (mirrors spec's 400 schema) | PASS |

Edge cases covered: empty peer list (non-nil JSON array), `count`/`peers` length invariant, blank vs. unknown agentID distinction, method not allowed, upgrade-path rejection ordering (400 before hijack), and handshake with proper WebSocket headers.
{"model": "deepseek-v4-flash", "problem_class": "go-openapi-docs-mesh-endpoints", "result": "passed", "tests": 11}
Generated from the verified corpus · MIT licensedBack to the catalog