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
171 lines
5.5 KiB
Go
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)
|
|
}
|