package pii import ( "encoding/json" "net/http" "go.uber.org/zap" "github.com/veylant/ia-gateway/internal/apierror" ) // AnalyzeRequest is the JSON body accepted by POST /v1/pii/analyze. type AnalyzeRequest struct { Text string `json:"text"` } // AnalyzeEntity is a single PII entity returned by the analyze endpoint. type AnalyzeEntity struct { Type string `json:"type"` Start int `json:"start"` End int `json:"end"` Confidence float64 `json:"confidence"` Layer string `json:"layer"` } // AnalyzeResponse is the JSON response of POST /v1/pii/analyze. type AnalyzeResponse struct { Anonymized string `json:"anonymized"` Entities []AnalyzeEntity `json:"entities"` } // AnalyzeHandler wraps a pii.Client as an HTTP handler for the playground. // It is safe to call when client is nil: returns the original text unchanged. type AnalyzeHandler struct { client *Client logger *zap.Logger } // NewAnalyzeHandler creates a new AnalyzeHandler. // client may be nil (PII service disabled) — the handler degrades gracefully. func NewAnalyzeHandler(client *Client, logger *zap.Logger) *AnalyzeHandler { return &AnalyzeHandler{client: client, logger: logger} } func (h *AnalyzeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var req AnalyzeRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { apierror.WriteError(w, apierror.NewBadRequestError("invalid JSON: "+err.Error())) return } if req.Text == "" { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(AnalyzeResponse{Anonymized: "", Entities: []AnalyzeEntity{}}) return } // PII service disabled — return text unchanged. if h.client == nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(AnalyzeResponse{ Anonymized: req.Text, Entities: []AnalyzeEntity{}, }) return } resp, err := h.client.Detect(r.Context(), req.Text, "playground", "playground-analyze", true, false) if err != nil { h.logger.Warn("PII analyze failed", zap.Error(err)) // Fail-open: return text unchanged rather than erroring. w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(AnalyzeResponse{ Anonymized: req.Text, Entities: []AnalyzeEntity{}, }) return } entities := make([]AnalyzeEntity, 0, len(resp.Entities)) for _, e := range resp.Entities { entities = append(entities, AnalyzeEntity{ Type: e.EntityType, Start: int(e.Start), End: int(e.End), Confidence: float64(e.Confidence), Layer: e.DetectionLayer, }) } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(AnalyzeResponse{ Anonymized: resp.AnonymizedText, Entities: entities, }) }