veylant/internal/pii/http.go
2026-02-23 13:35:04 +01:00

100 lines
2.8 KiB
Go

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,
})
}