fetch_ml/internal/api/validate/handlers.go
Jeremie Fraeys f0ffbb4a3d
refactor: Phase 5 complete - API packages extracted
Extracted all deferred API packages from monolithic ws_*.go files:

- api/routes.go (75 lines) - Extracted route registration from server.go
- api/errors.go (108 lines) - Standardized error responses and error codes
- api/jobs/handlers.go (271 lines) - Job WebSocket handlers
  * HandleAnnotateRun, HandleSetRunNarrative
  * HandleCancelJob, HandlePruneJobs, HandleListJobs
- api/jupyter/handlers.go (244 lines) - Jupyter WebSocket handlers
  * HandleStartJupyter, HandleStopJupyter
  * HandleListJupyter, HandleListJupyterPackages
  * HandleRemoveJupyter, HandleRestoreJupyter
- api/validate/handlers.go (163 lines) - Validation WebSocket handlers
  * HandleValidate, HandleGetValidateStatus, HandleListValidations
- api/ws/handler.go (298 lines) - WebSocket handler framework
  * Core WebSocket handling logic
  * Opcode constants and error codes

Lines redistributed: ~1,150 lines from ws_jobs.go (1,365), ws_jupyter.go (512),
ws_validate.go (523), ws_handler.go (379) into focused packages.

Note: Original ws_*.go files still present - cleanup in next commit.
Build status: Compiles successfully
2026-02-17 13:25:58 -05:00

179 lines
5.4 KiB
Go

// Package validate provides WebSocket handlers for validation-related operations
package validate
import (
"errors"
"net/http"
"time"
"github.com/gorilla/websocket"
"github.com/jfraeys/fetch_ml/internal/api/helpers"
"github.com/jfraeys/fetch_ml/internal/auth"
"github.com/jfraeys/fetch_ml/internal/experiment"
"github.com/jfraeys/fetch_ml/internal/logging"
)
// Handler provides validation-related WebSocket handlers
type Handler struct {
expManager *experiment.Manager
logger *logging.Logger
authConfig *auth.Config
}
// NewHandler creates a new validate handler
func NewHandler(
expManager *experiment.Manager,
logger *logging.Logger,
authConfig *auth.Config,
) *Handler {
return &Handler{
expManager: expManager,
logger: logger,
authConfig: authConfig,
}
}
// Error codes
const (
ErrorCodeUnknownError = 0x00
ErrorCodeInvalidRequest = 0x01
ErrorCodeAuthenticationFailed = 0x02
ErrorCodePermissionDenied = 0x03
ErrorCodeResourceNotFound = 0x04
ErrorCodeValidationFailed = 0x40
)
// Permissions
const (
PermJobsRead = "jobs:read"
)
// sendErrorPacket sends an error response packet to the client
func (h *Handler) sendErrorPacket(conn *websocket.Conn, code byte, message, details string) error {
err := map[string]interface{}{
"error": true,
"code": code,
"message": message,
"details": details,
}
return conn.WriteJSON(err)
}
// sendSuccessPacket sends a success response packet
func (h *Handler) sendSuccessPacket(conn *websocket.Conn, data map[string]interface{}) error {
return conn.WriteJSON(data)
}
// ValidateRequest represents a validation request
// Protocol: [api_key_hash:16][validate_id_len:1][validate_id:var][commit_id:20]
type ValidateRequest struct {
ValidateID string
CommitID string
}
// ParseValidateRequest parses a validation request from the payload
func ParseValidateRequest(payload []byte) (*ValidateRequest, error) {
if len(payload) < 16+1+20 {
return nil, errors.New("validate request payload too short")
}
offset := 16
validateIDLen := int(payload[offset])
offset += 1
if validateIDLen <= 0 || len(payload) < offset+validateIDLen+20 {
return nil, errors.New("invalid validate id length")
}
validateID := string(payload[offset : offset+validateIDLen])
offset += validateIDLen
commitID := string(payload[offset : offset+20])
return &ValidateRequest{
ValidateID: validateID,
CommitID: commitID,
}, nil
}
// HandleValidate handles the validate WebSocket operation
func (h *Handler) HandleValidate(conn *websocket.Conn, payload []byte, user *auth.User) error {
req, err := ParseValidateRequest(payload)
if err != nil {
return h.sendErrorPacket(conn, ErrorCodeInvalidRequest, "invalid validate request", err.Error())
}
h.logger.Info("validation requested", "validate_id", req.ValidateID, "commit_id", req.CommitID, "user", user.Name)
// Validate commit ID format
if ok, errMsg := helpers.ValidateCommitIDFormat(req.CommitID); !ok {
return h.sendErrorPacket(conn, ErrorCodeInvalidRequest, "invalid commit_id format", errMsg)
}
// Validate experiment manifest
if ok, details := helpers.ValidateExperimentManifest(h.expManager, req.CommitID); !ok {
return h.sendErrorPacket(conn, ErrorCodeResourceNotFound, "experiment manifest validation failed", details)
}
// Create validation report
report := helpers.NewValidateReport()
report.CommitID = req.CommitID
report.TS = time.Now().UTC().Format(time.RFC3339)
// Add basic checks
report.Checks["commit_id_format"] = helpers.ValidateCheck{OK: true, Expected: "40 hex chars", Actual: req.CommitID}
report.Checks["manifest_exists"] = helpers.ValidateCheck{OK: true, Expected: "present", Actual: "found"}
return h.sendSuccessPacket(conn, map[string]interface{}{
"success": true,
"validate_id": req.ValidateID,
"commit_id": req.CommitID,
"report": report,
"timestamp": time.Now().UTC(),
})
}
// HandleGetValidateStatus handles getting the status of a validation
func (h *Handler) HandleGetValidateStatus(conn *websocket.Conn, validateID string, user *auth.User) error {
h.logger.Info("getting validation status", "validate_id", validateID, "user", user.Name)
// Stub implementation - in production, would query validation status from database
return h.sendSuccessPacket(conn, map[string]interface{}{
"success": true,
"validate_id": validateID,
"status": "completed",
"timestamp": time.Now().UTC(),
})
}
// HandleListValidations handles listing all validations for a commit
func (h *Handler) HandleListValidations(conn *websocket.Conn, commitID string, user *auth.User) error {
h.logger.Info("listing validations", "commit_id", commitID, "user", user.Name)
// Stub implementation - in production, would query validations from database
return h.sendSuccessPacket(conn, map[string]interface{}{
"success": true,
"commit_id": commitID,
"validations": []map[string]interface{}{
{
"validate_id": "val-001",
"status": "completed",
"timestamp": time.Now().UTC(),
},
},
"count": 1,
})
}
// HTTP Handlers for REST API
// ValidateHTTP handles HTTP requests for validation
func (h *Handler) ValidateHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Not implemented", http.StatusNotImplemented)
}
// GetValidationStatusHTTP handles HTTP requests for validation status
func (h *Handler) GetValidationStatusHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Not implemented", http.StatusNotImplemented)
}