97 lines
2.6 KiB
Go
97 lines
2.6 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
|
|
"chis_osi/pipeline"
|
|
"chis_osi/source"
|
|
)
|
|
|
|
const maxHealthRecordUpsertBody = 1 << 20
|
|
|
|
type HealthRecordUpserter interface {
|
|
Upsert(context.Context, source.HealthRecordTask) (pipeline.Outcome, error)
|
|
}
|
|
|
|
type HealthRecordUpsertHandler struct {
|
|
upserter HealthRecordUpserter
|
|
}
|
|
|
|
func NewHealthRecordUpsertHandler(upserter HealthRecordUpserter) *HealthRecordUpsertHandler {
|
|
return &HealthRecordUpsertHandler{upserter: upserter}
|
|
}
|
|
|
|
func (h *HealthRecordUpsertHandler) Upsert(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
w.Header().Set("Allow", http.MethodPost)
|
|
writeJSONError(w, http.StatusMethodNotAllowed, "only POST is supported")
|
|
return
|
|
}
|
|
if h.upserter == nil {
|
|
writeJSONError(w, http.StatusServiceUnavailable, "health record upsert is unavailable")
|
|
return
|
|
}
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxHealthRecordUpsertBody)
|
|
raw, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
var maxBytesErr *http.MaxBytesError
|
|
if errors.As(err, &maxBytesErr) {
|
|
writeJSONError(w, http.StatusRequestEntityTooLarge, "request body exceeds 1 MiB")
|
|
return
|
|
}
|
|
writeJSONError(w, http.StatusBadRequest, "read request body failed")
|
|
return
|
|
}
|
|
task, err := source.DecodeHealthRecordTask(raw)
|
|
if err != nil {
|
|
writeJSONError(w, http.StatusBadRequest, "invalid PHIS health record envelope")
|
|
return
|
|
}
|
|
|
|
outcome, err := h.upserter.Upsert(r.Context(), task)
|
|
if err != nil {
|
|
response := healthRecordUpsertErrorResponse{Error: "health record upsert failed", RetrySafe: outcome.Status == ""}
|
|
if outcome.Status != "" {
|
|
response.Outcome = &outcome
|
|
}
|
|
writeJSON(w, http.StatusBadGateway, response)
|
|
return
|
|
}
|
|
writeJSON(w, outcomeHTTPStatus(outcome), outcome)
|
|
}
|
|
|
|
type healthRecordUpsertErrorResponse struct {
|
|
Error string `json:"error"`
|
|
RetrySafe bool `json:"retrySafe"`
|
|
Outcome *pipeline.Outcome `json:"outcome,omitempty"`
|
|
}
|
|
|
|
func outcomeHTTPStatus(outcome pipeline.Outcome) int {
|
|
switch outcome.Status {
|
|
case pipeline.StatusDone:
|
|
return http.StatusOK
|
|
case pipeline.StatusManualReview:
|
|
return http.StatusConflict
|
|
case pipeline.StatusRetry:
|
|
return http.StatusServiceUnavailable
|
|
case pipeline.StatusFailed:
|
|
if outcome.ServiceID != "" {
|
|
return http.StatusBadGateway
|
|
}
|
|
return http.StatusUnprocessableEntity
|
|
default:
|
|
return http.StatusInternalServerError
|
|
}
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, value any) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(value)
|
|
}
|