fetch_ml/internal/api/validate/handlers.go
Jeremie Fraeys c18a8619fe
feat(api): add structured error package and refactor handlers
New error handling:
- Add internal/api/errors/errors.go with structured API error types
- Standardize error codes across all API endpoints
- Add user-facing error messages vs internal error details separation

Handler improvements:
- jupyter/handlers.go: better workspace lifecycle and error handling
- plugins/handlers.go: plugin management with validation
- groups/handlers.go: group CRUD with capability metadata
- jobs/handlers.go: job submission and monitoring improvements
- datasets/handlers.go: dataset upload/download with progress
- validate/handlers.go: manifest validation with detailed errors
- audit/handlers.go: audit log querying with filters

Server configuration:
- server_config.go: refined config loading with validation
- server_gen.go: improved code generation for OpenAPI specs
2026-03-12 12:04:46 -04:00

171 lines
5.5 KiB
Go

// Package validate provides WebSocket handlers for validation-related operations
package validate
import (
"errors"
"net/http"
"time"
"github.com/gorilla/websocket"
apierrors "github.com/jfraeys/fetch_ml/internal/api/errors"
"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 - using standardized error codes from errors package
const (
ErrorCodeUnknownError = apierrors.CodeUnknownError
ErrorCodeInvalidRequest = apierrors.CodeInvalidRequest
ErrorCodeAuthenticationFailed = apierrors.CodeAuthenticationFailed
ErrorCodePermissionDenied = apierrors.CodePermissionDenied
ErrorCodeResourceNotFound = apierrors.CodeResourceNotFound
ErrorCodeValidationFailed = apierrors.CodeInvalidConfiguration
)
// Permissions
const (
PermJobsRead = "jobs:read"
)
// sendErrorPacket sends an error response packet to the client
func (h *Handler) sendErrorPacket(conn *websocket.Conn, code string, message, details string) error {
return apierrors.SendErrorPacket(conn, code, message, details)
}
// sendSuccessPacket sends a success response packet
func (h *Handler) sendSuccessPacket(conn *websocket.Conn, data map[string]any) error {
return apierrors.SendSuccessPacket(conn, 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]any{
"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]any{
"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]any{
"commit_id": commitID,
"validations": []map[string]any{
{
"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)
}